简体   繁体   English

如何在 ASP.NET Core 集成测试中覆盖来自其他容器的 DI 注册

[英]How to override DI registration from other container in ASP.NET Core integration test

I have the following registration in asp.net core startup.cs file:我在 asp.net core startup.cs 文件中有以下注册:

    public void ConfigureContainer(ContainerBuilder builder)
    {
       builder.RegisterType<UserService>().As<IUserService>();
    }

This is to configure the Autofac container.这是配置 Autofac 容器。 And I have another integration test project where I have a CustomWebApplicationFactory class and I'm trying to replace the implementation of IUserService interface.我还有另一个集成测试项目,其中有一个 CustomWebApplicationFactory 类,我正在尝试替换 IUserService 接口的实现。

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddSingleton<IUserService, TestUsersService>();
        });
    }

It seems not work when I debug the test project and the implementation of IUserService is still UserService.调试测试项目好像不行,IUserService的实现还是UserService。

I tried registering UserService directly in Startup.ConfigureServices method with ASP.NET Core built-in IServiceCollection and it worked when debugging:我尝试使用 ASP.NET Core 内置 IServiceCollection 直接在 Startup.ConfigureServices 方法中注册 UserService 并且它在调试时工作:

services.AddSingleton<IUserService, UserService>();

So, how can I fix the problem when I use Autofac as the IoC container and the integration test project will work properly as I expect?那么,当我使用 Autofac 作为 IoC 容器并且集成测试项目将按预期正常工作时,我该如何解决问题?

You are likely running into an order of operations problem.您可能会遇到操作顺序问题。 In general, last in wins.一般来说,最后获胜。 That goes for Autofac and for the base Microsoft DI container.这适用于 Autofac 和基本的 Microsoft DI 容器。

Assuming you've read through the docs on Autofac ASP.NET Core integration you'll see that when ConfigureContainer is in place the order of operations is roughly:假设您已阅读有关 Autofac ASP.NET Core 集成的文档,您将看到当ConfigureContainer就位时,操作顺序大致如下:

  • WebHost specific ConfigureServices特定于 WebHost 的 ConfigureServices
  • Startup class ConfigureServices启动类 ConfigureServices
  • Startup class ConfigureContainer启动类 ConfigureContainer

When adding ConfigureTestServices in place, it looks like (though I haven't stepped through) it runs after the WebHost and Startup class ConfigureServices... but it's still running before ConfigureContainer.在适当的位置添加 ConfigureTestServices 时,它看起来(虽然我没有逐步完成)它在 WebHost 和启动类 ConfigureServices 之后运行......但它仍然在 ConfigureContainer 之前运行。

This would be easy enough to test - create a service interface with three different implementations.这很容易测试 - 创建一个具有三种不同实现的服务接口。 Register a different implementation at each level.在每个级别注册不同的实现。 Resolve the interface in a controller.解析控制器中的接口。 Which one did you get?你得到了哪一个? That was the last one to run.那是最后一个运行。 Now remove that registration from the app and try again.现在从应用程序中删除该注册并重试。 What's the next one you get?你得到的下一个是什么? That's the second to the last one.这是倒数第二个。 And so on.等等。

Autofac takes a pre-built IServicesCollection and cycles through it, adding that to the native Autofac container. Autofac 采用预先构建的IServicesCollection并循环遍历它,将其添加到本机 Autofac 容器中。 Once that's happened, it doesn't matter if you modify the collection.一旦发生这种情况,您是否修改集合都没有关系。 Autofac has no control over the order of the execution of the startup mechanism in ASP.NET Core; Autofac 无法控制 ASP.NET Core 中启动机制的执行顺序; it just knows that ASP.NET Core says, "Here's the final service collection to go ahead and import!"它只知道 ASP.NET Core 说:“这是要继续导入的最终服务集合!” If that's not happening at the right stage, you'll have to do one of two things:如果这不是在正确的阶段发生,您将不得不做以下两件事之一:

  • Move the registration you need to override out of ConfigureContainer and into one of the ConfigureServices methods, using the Microsoft registration language instead of native Autofac.使用 Microsoft 注册语言而不是本机 Autofac,将您需要覆盖的注册从ConfigureContainer移到ConfigureServices方法之一。
  • Perform the override some other way like using an ASPNETCORE_ENVIRONMENT setting of Test and providing a ConfigureTestContainer method.以其他方式执行覆盖,例如使用TestASPNETCORE_ENVIRONMENT设置并提供ConfigureTestContainer方法。 (Examples of environment specific registration methods are in the docs .) (环境特定注册方法的示例在文档中。)

When using ContainerBuilder like this:像这样使用 ContainerBuilder 时:

    public void ConfigureContainer(ContainerBuilder builder)
    {
        builder.RegisterType<UserService>().As<IUserService>();
    }

You must use ConfigureTestContainer rather than ConfigureTestServices as follows:您必须使用 ConfigureTestContainer 而不是 ConfigureTestServices,如下所示:

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestContainer<ContainerBuilder>(containerBuilder =>
            {
                containerBuilder.RegisterType<TestUsersService>().As<IUserService>();
            });
    }

This is executed after the call to ConfigureContainer and will correctly override IUserService with TestUsersService这是在调用 ConfigureContainer 之后执行的,并将使用TestUsersService正确覆盖IUserService

For the ones coming from google I'd like to add to Michael's excellent answer that ConfigureTestContainer does not work with the generic host recommended over the web host by Microsoft as of .NET Core 3.0.对于来自 google 的那些,我想添加到 Michael 的出色回答中,即ConfigureTestContainer不适用于Microsoft从 .NET Core 3.0 开始通过 Web 主机推荐的通用主机。 There is however a workaround proposed by Alistair Evans from the Autofac team .然而,Autofac 团队的 Alistair Evans 提出了一种解决方法 Unfortunately, it relies on the deprecated IStartupConfigureContainerFilter that will probably be removed in .NET 5.0.不幸的是,它依赖于可能会在 .NET 5.0 中删除的已弃用的IStartupConfigureContainerFilter

This means that currently in .NET 5.0 there is no way to mock dependencies injected by an external DI container in integration tests when using the generic host.这意味着当前在 .NET 5.0 中,当使用通用主机时,无法在集成测试中模拟外部 DI 容器注入的依赖项。

Luckily, David Fowler from the ASP.NET team is looking into the issue .幸运的是,来自 ASP.NET 团队的 David Fowler 正在调查这个问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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