【发布时间】:2019-08-31 03:12:24
【问题描述】:
我正在尝试编写一个 ASP.NET Core 2.2 集成测试,其中测试设置装饰了一个特定的服务,该服务通常作为依赖项可供 API 使用。装饰器会给我一些额外的权力,我需要在集成测试中拦截对底层服务的调用,但 我似乎无法正确装饰 ConfigureTestServices 中的普通服务,因为我的目前的设置会给我:
Microsoft.Extensions.DependencyInjection.Abstractions.dll 中出现“System.InvalidOperationException”类型的异常,但未在用户代码中处理
没有注册类型“Foo.Web.BarService”的服务。
为了重现这一点,我刚刚使用 VS2019 创建了一个全新的 ASP.NET Core 2.2 API Foo.Web 项目...
// In `Startup.cs`:
services.AddScoped<IBarService, BarService>();
public interface IBarService
{
string GetValue();
}
public class BarService : IBarService
{
public string GetValue() => "Service Value";
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IBarService barService;
public ValuesController(IBarService barService)
{
this.barService = barService;
}
[HttpGet]
public ActionResult<string> Get()
{
return barService.GetValue();
}
}
...和一个配套的 xUnit Foo.Web.Tests 项目 I utilize a WebApplicationfactory<TStartup>...
public class DecoratedBarService : IBarService
{
private readonly IBarService innerService;
public DecoratedBarService(IBarService innerService)
{
this.innerService = innerService;
}
public string GetValue() => $"{innerService.GetValue()} (decorated)";
}
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(di.GetRequiredService<BarService>()));
});
}
}
public class ValuesControllerTests : IClassFixture<IntegrationTestsFixture>
{
private readonly IntegrationTestsFixture fixture;
public ValuesControllerTests(IntegrationTestsFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public async Task Integration_test_uses_decorator()
{
var client = fixture.CreateClient();
var result = await client.GetAsync("/api/values");
var data = await result.Content.ReadAsStringAsync();
result.EnsureSuccessStatusCode();
Assert.Equal("Service Value (decorated)", data);
}
}
这种行为是有道理的,或者至少我认为是有道理的:我想ConfigureTestServices 中的小工厂 lambda 函数 (di => new DecoratedBarService(...)) 无法从di 容器,因为它在主服务集合中,而不是在测试服务中。
如何让默认的 ASP.NET Core DI 容器提供具有原始具体类型作为其内部服务的装饰器实例?
尝试的解决方案 2:
我尝试了以下方法:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<BarService>()));
});
}
但这令人惊讶地遇到了同样的问题。
尝试的解决方案 3:
改为请求IBarService,如下所示:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<IBarService>()));
});
}
给我一个不同的错误:
System.InvalidOperationException: 'Cannot resolve scoped service 'Foo.Web.IBarService' from root provider.'
解决方法 A:
我可以像这样在我的小型复制中解决这个问题:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(new BarService()));
});
}
但这在我的实际应用程序中伤害很大,因为BarService没有简单的无参数构造函数:它有一个中等复杂的依赖图,所以我真的很想从Startup的DI容器中解析实例。
附言。我试图让这个问题完全独立,但为了您的方便,还有a clone-and-run rep(r)o。
【问题讨论】:
标签: c# asp.net-core .net-core integration-testing xunit