繁体   English   中英

如何验证 ASP.NET Core 中的 DI 容器?

[英]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);
    }
}

我在我的一个项目中遇到了同样的问题。 我的决心:

  1. 添加 AddScopedService、AddTransientService 和 AddSingletonService 等方法,将服务添加到 DI,然后将其添加到某个列表。 使用此方法代替 AddScoped、AddSingleton 和 AddTransient

  2. 在第一次启动应用程序时,我遍历这个列表并调用 GetRequiredService。 如果任何服务无法解决,应用程序将不会启动

  3. 我有 CI: auto build and deploy on commit to develop 分支。 因此,如果有人合并破坏 DI 的更改,应用程序将失败,我们都知道。

  4. 如果你想做得更快,你可以在 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM