[英]How do I validate the DI container in ASP.NET Core?
在我的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);
}
ASP.NET Core 3 中添加了内置 DI 容器验证,默认情况下仅在Development
环境中启用。 如果缺少某些东西,容器在启动时会抛出一个致命的异常。
请记住,默认情况下控制器不会在 DI 容器中创建,因此在控制器在 DI 中注册之前,典型的 Web 应用程序不会从该检查中获得太多信息:
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/
要确保ASP.NET Core应用程序按预期工作并且所有依赖项都是正确注入的,您应该在ASP.NET Core中使用Integration测试 。 这样您就可以使用相同的Startup
类初始化TestServer
,因此它会导致所有依赖项被注入(没有模拟或类似的存根),并使用公开的路由/ 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));
实际上,我只是使用了您问题中的示例并进行了一些修改,对我来说效果很好。 在我的命名空间中按类过滤背后的理论是,那些最终会要求我关心的其他一切。
我的测试看起来很像这样:
[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);
}
}
我在我的一个项目中遇到了同样的问题。 我的决心:
添加 AddScopedService、AddTransientService 和 AddSingletonService 等方法,将服务添加到 DI,然后将其添加到某个列表。 使用此方法代替 AddScoped、AddSingleton 和 AddTransient
在第一次启动应用程序时,我遍历这个列表并调用 GetRequiredService。 如果任何服务无法解决,应用程序将不会启动
我有 CI: auto build and deploy on commit to develop 分支。 因此,如果有人合并破坏 DI 的更改,应用程序将失败,我们都知道。
如果你想做得更快,你可以在 Dmitry Pavlov 的回答中使用 TestServer 和我的解决方案
这是您可以添加到项目中的单元测试:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using [X/N]Unit;
namespace MyProject.UnitTests
{
public class DITests
{
[Fact or Test]
public void AllServicesShouldConstructSuccessfully()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseDefaultServiceProvider((context, options) =>
{
options.VailidateOnBuild = true;
})
.UseStartup<Startup>();
}).Build();
})
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.