Пересчет и устранение зависимости Autofac в интеграционном тесте в AspNetCore с помощью TestServer

Я использую AspNetCore 2.2. Следую (более) документам здесь: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2

Я использую Autofac, мой класс запуска имеет следующие методы:

public void ConfigureServices(IServiceCollection services)
public void ConfigureContainer(ContainerBuilder containerBuilder) //this is where things can be registered directly with autofac and runs after ConfigureServices
public void Configure(...) //the method called by runtime

Как я рекомендую в своих документах, я использую Autofac, имея Program.cs like this

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel()
            .ConfigureServices(services => services.AddAutofac())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .ConfigureAppConfiguration((builderContext, config) =>
            {
                var env = builderContext.HostingEnvironment;
                config
                    .AddJsonFile("appsettings.json", false, true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
                    .AddEnvironmentVariables();
            });
}

Сейчас я использую TestServer для моего тестового проекта, где я хочу использовать настоящий класс Startup. Но я бы хотел изменить одну из зависимостей.

Я видел, что есть метод, доступный на WebHostBuilder в .ConfigureTestServices(services => {});

поэтому я попробовал следующее:

public abstract class ComponentTestFeature
    : Feature
{
    protected HttpClient HttpClient { get; }

    protected ComponentTestFeature()
    {
        var configuration =
            new ConfigurationBuilder()
                .AddJsonFile("appsettings.Test.json")
                .Build();

        var webHostBuilder =
            new WebHostBuilder()
                .ConfigureServices(services => services.AddAutofac())
                .ConfigureTestServices(services =>
                {
                    services.AddScoped<IEventStoreManager, MockEventStoreManager>();
                })
                .UseConfiguration(configuration)
                .UseStartup<Startup>();

        var server = new TestServer(webHostBuilder);
        HttpClient = server.CreateClient();
        var myService = server.Host.Services.GetRequiredService<IEventStoreManager>();
    }
}

Как вы можете видеть, я хочу использовать MockEventStoreManager реализация для IEventStoreManager так что это реализация, которая должна быть разрешена контейнером. Однако это работает не так, как ожидалось, и myService решил исходную, реальную, а не фиктивную реализацию.

Кто-нибудь знает, как я могу добиться того, чего хочу?

ОБНОВЛЕНИЕ 1: После дополнительных исследований мне пришлось создать проблему на github AspNetCore, потому что метод расширения выполняется до ConfigureContainer, поэтому я не могу переопределить зависимости autofac и не получить контейнер autofac позже. https://github.com/aspnet/AspNetCore/issues/6522

ОБНОВЛЕНИЕ 2: Только к вашему сведению, так выглядит Startup.cs. Как видите, все зависимости, кроме mvc, зарегистрированы в контейнере autofac.

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

// This is where things can be registered directly with autofac and runs after ConfigureServices, so it will override it
public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterType<EventStoreManager>().As<IEventStoreManager>();
}

и что я хочу сделать, это использовать MockEventStoreManager реализация для IEventStoreManager в моих тестах, поэтому мне нужно переопределить (из моего тестового проекта), что регистрация Autofac хорошим способом.

2 ответа

Решение

Зарегистрируйте ложную реализацию в конструкторе контейнеров для теста с использованием ConfigureTestContainer

//...
.ConfigureServices(services => services.AddAutofac())
.ConfigureTestContainer<ContainerBuilder>(builder => {
    builder.RegisterType<MockEventStoreManager>().As<IEventStoreManager>();
})
//...

Это должно избежать получения фактической реализации, которая добавляется Startup.ConfigureContainer как

Если более одного компонента предоставляют одну и ту же услугу, Autofac будет использовать последний зарегистрированный компонент в качестве поставщика этой услуги по умолчанию:

Регистрация по умолчанию

ConfigureTestContainer вызывается после Startup.ConfigureContainer поэтому последняя регистрация в макете будет поставщиком услуг по умолчанию.

Добавляя к отличному ответу Nkosi, я хотел бы упомянуть, что ConfigureTestContainerне работает с общим хостом, рекомендованным Microsoft для веб-хоста с.NET Core 3.0. Однако есть обходной путь, предложенный Алистером Эвансом из команды Autofac. К сожалению, он полагается на устаревшие IStartupConfigureContainerFilter это, вероятно, будет удалено в.NET 5.0.

Это означает, что в настоящее время в.NET 5.0 нет возможности имитировать зависимости, внедренные внешним контейнером DI в интеграционные тесты при использовании универсального хоста.

К счастью, Дэвид Фаулер из команды ASP.NET изучает проблему.

Есть несколько вариантов:

  • Имейте пользовательскую реализацию вашей зависимости в вашем проекте testserver

  • Реализовать и вручную зарегистрировать ложную реализацию вашей зависимости с помощью слепка, такого как "Moq".

Другие вопросы по тегам