在我的Startup
课程中,我使用ConfigureServices(IServiceCollection services)
方法设置我的服务容器,使用Microsoft.Extensions.DependencyInjection
中的内置DI容器。
我想在单元测试中验证依赖图,以检查是否可以构造所有服务,这样我就可以修复在单元测试期间丢失的任何服务,而不是让应用程序在运行时崩溃。在以前的项目中,我使用过Simple Injector,它对容器有一个.Verify()
方法。但我还没能找到类似ASP.NET Core的东西。
是否有任何内置(或至少推荐)的方式来验证是否可以构建整个依赖图?
(我能想到的最愚蠢的方式就是这样,但由于框架本身注入的开放式泛型,它仍然会失败):
startup.ConfigureServices(serviceCollection);
var provider = serviceCollection.BuildServiceProvider();
foreach (var serviceDescriptor in serviceCollection)
{
provider.GetService(serviceDescriptor.ServiceType);
}
答案 0 :(得分:9)
内置的DI容器验证已添加到ASP.NET Core 3中,并且默认情况下仅在Development
环境中启用。如果缺少某些内容,则容器在启动时会引发致命异常。
请记住,默认情况下不会在DI容器中创建控制器,因此,典型的Web应用程序在此检查不会获得很多收益,除非将控制器注册到DI中:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddControllersAsServices();
}
要禁用/自定义验证,请添加一个IHostBuilder.UseDefaultServiceProvider
调用:
public class Program
{
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
//...
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = false;
});
此验证功能有几个限制,请在此处阅读更多信息:https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
答案 1 :(得分:1)
实际上,我只是使用您问题中的示例进行了一些修改,对我来说效果很好。在我的命名空间中按类进行过滤的理论是,这些过滤器最终会询问我关心的所有其他问题。
我的测试看起来像这样:
[Test or Fact or Whatever]
public void AllDependenciesPresentAndAccountedFor()
{
// Arrange
var startup = new Startup();
// Act
startup.ConfigureServices(serviceCollection);
// Assert
var exceptions = new List<InvalidOperationException>();
var provider = serviceCollection.BuildServiceProvider();
foreach (var serviceDescriptor in services)
{
var serviceType = serviceDescriptor.ServiceType;
if (serviceType.Namespace.StartsWith("my.namespace.here"))
{
try
{
provider.GetService(serviceType);
}
catch (InvalidOperationException e)
{
exceptions.Add(e);
}
}
}
if (exceptions.Any())
{
throw new AggregateException("Some services are missing", exceptions);
}
}
答案 2 :(得分:0)
要确保ASP.NET Core应用程序按预期工作并且所有依赖项都是正确注入的,您应该使用Integration testing in ASP.NET Core。这样,您可以使用相同的TestServer
类初始化Startup
,因此它会导致所有依赖项被注入(没有模拟或类似的存根),并使用公开的路由/ URL /路径测试您的应用程序。
默认根URL的集成测试可能如下所示:
public class PrimeWebDefaultRequestShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebDefaultRequestShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Hello World!", responseString);
}
}
如果你需要检索通过DI
注入的特定服务,你可以这样做:
var service = _server.Host.Services.GetService(typeof(IYourService));
答案 3 :(得分:0)
我在一个项目中遇到了同样的问题。我的决心:
添加诸如AddScopedService,AddTransientService和AddSingletonService之类的方法,这些方法将服务添加到DI,然后将其添加到某些List。使用此方法代替AddScoped,AddSingleton和AddTransient
循环访问此列表并调用GetRequiredService。如果无法解决任何服务,则应用程序将无法启动
我有CI:提交后自动构建并部署到开发分支。因此,如果有人合并破坏了DI的更改,则应用程序将失败,我们都知道。
如果希望更快地执行此操作,则可以将Dmitry Pavlov的答案中的TestServer与我的解决方案一起使用