Mocking and resolving Autofac dependency in integration test in AspNetCore with TestServer
Asked Answered
T

3

12

I'm using AspNetCore 2.2 Following (moreless) the docs here: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2

I am using Autofac, my Startup class has the following methods:

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

The way I use Autofac, as recommended by its docs, is by having 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();
            });
}

I am now using TestServer for my test project where I want to use the real Startup class. But I would like to modify one of the dependencies.

I have seen that there is a method available on WebHostBuilder to .ConfigureTestServices(services => {});

so I tried the following:

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>();
    }
}

As you can see I want to use a MockEventStoreManager implementation for IEventStoreManager so that this is the implementation that should be resolved by the container. However this is not working as expected and myService resolved is the original implementation, the real one, not the mock one.

Does anybody know how could I achieve what I want?

UPDATE 1: After more investigation I had to create an issue on github AspNetCore because the extension method is executed before the ConfigureContainer, therefore I cannot really override autofac dependencies nor retrieve autofac container later on. https://github.com/aspnet/AspNetCore/issues/6522

UPDATE 2: Just FYI, this is how the Startup.cs looks like. As you can see all the dependencies but mvc are registered in autofac's container.

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>();
}

and what I want to do is to use MockEventStoreManager implementation for IEventStoreManager in my tests, so I need to override (from my test project) that Autofac's registration in a nice way.

Thready answered 9/1, 2019 at 10:22 Comment(3)
Where in the flow is the original IEventStoreManager registered? ConfigureServices or directly with the container in ConfigureContainer?Glendaglenden
In ConfigureContainer, with autofac containerThready
Just like ConfigureTestServices, there is a ConfigureTestContainer<TContainer> that can be used to directly manipulate the container. Check updated answer.Glendaglenden
G
16

Register the mocked implementation with the container builder for the test using ConfigureTestContainer

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

This should avoid getting the actual implementation that is added by Startup.ConfigureContainer as

If more than one component exposes the same service, Autofac will use the last registered component as the default provider of that service:

Reference Default Registrations

ConfigureTestContainer is invoked after the Startup.ConfigureContainer so the last registration with the mock would be the default provider of the service.

Glendaglenden answered 17/1, 2019 at 19:53 Comment(1)
For Asp .Net Core 3.0+ or Generic hosting services.AddAutofac() does not work. From Autofac code documentation: "ONLY FOR PRE-ASP.NET 3.0 HOSTING. THIS WON'T WORK FOR ASP.NET CORE 3.0+ OR GENERIC HOSTING."Astomatous
M
9

Adding to Nkosi's excellent answer, I'd like to mention that ConfigureTestContainer does not work with the generic host recommended over the web host by Microsoft as of .NET Core 3.0. There is however a workaround proposed by Alistair Evans from the Autofac team. Unfortunately, it relies on the deprecated IStartupConfigureContainerFilter that has been removed in .NET 5.0.

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.

Luckily, David Fowler from the ASP.NET team is looking into the issue.

Major answered 12/10, 2020 at 10:19 Comment(1)
In the meantime, the answer here worked for me to mock Autofac dependencies: https://mcmap.net/q/1007422/-how-to-override-a-service-in-autofac-container-using-webapplicationfactoryMinute
H
0

There are a couple of options:

  • Have a custom implementation of your dependency in your testserver project

  • Implement and manually register a mocked implementation of your dependency with a nugget like "Moq"

Haemostasis answered 17/1, 2019 at 12:38 Comment(1)
I have a custom implementation of the interface in my testserver project. It's in fact using Moq internally not to use a real connection to an external system. The problem is that I cannot tell my application, when testing, that the concrete implementation to be used is the mocked one instead the one registered on Startup.cs' ConfigureContainer. In other words, I am unable to override autofac's container from TestServerThready

© 2022 - 2024 — McMap. All rights reserved.