Search code examples
testingasp.net-coreautofacxunit

How to properly configure mock dependencies for testing Web Api (ASP.NET Core) controllers using Autofac


I'm using ASP.NET Core (2.0) with Autofac, and a Microsoft.AspNetCore.TestHost.TestServer for integration testing. However, for some test scenarios, I would like to inject some service mocks instead of the implementations loaded in ConfigureContainer method (as described here: http://docs.autofac.org/en/latest/integration/aspnetcore.html#quick-start-with-configurecontainer).

Example:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .ConfigureServices(s => s.AddAutofac())
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

public class Startup
{
    ...
    public void ConfigureContainer(ContainerBuilder builder)
    {
        builder.RegisterModule(new Modules.ApiModule());
    }
    ...
}

And the test class:

public class BasicControllerTests
{
    TestServer server;
    HttpClient client;

    public BasicControllerTests()
    {
        var resellerRepo = new Mock<IResellerProvider>();
        resellerRepo.Setup(a => a.Query())
            .Returns(new[] {
            new Model.Reseller
            {
                Id = Guid.NewGuid(),
                Code = "R1",
                Name = "Reseller 1"
            }
            }.AsQueryable());

        // How to inject mock properly in the lines below?

        server = new TestServer(new WebHostBuilder()
            .ConfigureServices(a => a.AddAutofac())
            .UseStartup<Startup>());

        client = server.CreateClient();
    }
   ...

What I would like to do is to use the TestServer with all the dependencies as they are, but just the IResellerProvider mocked as in the test example. What is the best way to accomplish that? Of course, I could create a TestStartup class for this exact case, but I would like to know what is the proper way to handle this situation.


Solution

  • I found a workaround that works just fine and lets you inject any other dependency in .net core api. You will have this standard code to start up in your tests

    var clientFactory = new WebApplicationFactory<Startup>();
    var client = clientFactory.WithWebHostBuilder(builder =>
    builder.ConfigureTestServices(services =>
    {
    
    }));
    
    var _httpClient = client.CreateClient();
    

    Now you need to pass to .ConfigureTestServices an Action and you can use this to remove the registration you have on the normal app startup and add another one that lets say it's a fake one.This in possible because if you look with debugger on services you will see that all those you registered are presend and you will just need to replace the ones you want to moke.Here is a simple example that I used

    RemoveVehicleServiceRegistrationFrom(services);
    services.AddScoped<IVehicleService, FakeVehicleService>();
    

    In the Remove method you just need to find and remove old registration.Something like this

    private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
    {
         var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
         services.Remove(vehicleService);
    }
    

    Final version looks like this

    private HttpClient _httpClient;
    
    [OneTimeSetUp]
    public void Setup()
    {
        var clientFactory = new WebApplicationFactory<Startup>();
        var client = clientFactory.WithWebHostBuilder(builder =>
            builder.ConfigureTestServices(services =>
            {
                RemoveVehicleServiceRegistrationFrom(services);
                services.AddScoped<IVehicleService, FakeVehicleService>();
            }));
    
        var _httpClient = client.CreateClient();
    }
    
    private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
    {
        var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
        services.Remove(vehicleService);
    }