Alica's dev blog
Configuring mock services in WebApplicationFactory

Introduction

WebApplicationFactory (from Microsoft.AspNetCore.Mvc.Testing) is a great tool for testing ASP.NET (Core) WebAPIs (I have a whole series on integration tests with ASP.NET (Core) and SQL Server, with one part specifically dedicated to WebApplicationFactory).

It’s easy create an HTTP client from the factory. The basic setup is just two lines:

var factory = new WebApplicationFactory<Startup>();
HttpClient client = factory.CreateClient();

If we want some customization, we can create an inherited class TestApplicationFactory, override ConfigureWebHost method and do the setup there:

public class TestApplicationFactory : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((context, configurationBuilder) =>
        {
            // can add some custom configuration...
        });

        builder.ConfigureServices((context, services) =>
        {
            // can do something based on the context or the configuration...
        });
    }
}

For example, you can configure the test application to use a different connection string for the database.

Problem

What if we need to create multiple kinds of clients (and therefore factories)?

For example, we want all our tests for working with the data to bypass the authorization, with the exception of the actual tests for the authorization.

We could create a whole new factory for the client which would be used for authorization tests. However, the authorization service is the only difference from the “original” (default) client. We still want to use default configuration of the original factory that bypasses the authorization, except we don’t want to bypass it.

Therefore, we need to be able to inject some configuration to the factory from the outside.

Solution

We add a constructor to TestApplicationFactory that takes Action<IServiceCollection> as a parameter. We will then use this parameter when calling builder.ConfigureServices():

public class TestApplicationFactory : WebApplicationFactory<Startup>
{
    // delegate that can be injected from outside
    private readonly Action<IServiceCollection> configureServices;

    // parameterless constructor in case we want to use the default setting
    public TestApplicationFactory() { }

    public TestApplicationFactory(Action<IServiceCollection> configureServices) 
    { 
        this.configureServices = configureServices;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((context, configurationBuilder) => { ... });

        builder.ConfigureServices((context, services) =>
        {
            // e.g. set the tests to bypass the authorization
        });

        // don't forget to check for null
        if (configureServices is null) return;

        // use configureServices delegate
        builder.ConfigureServices(configureServices);
    }
}

Now the only question is, how to implement this configureServices delegate?

Working with the services

In all the examples that follow, services varaible is of type that implements IServiceCollection.

If we have a interface that has only one implementation registered, we can just call Replace method that removes the existing implementation and registers another one.

services.Replace(ServiceDescriptor.Scoped<IUserService, MockUserService>);

We don’t even have to create a new class for the custom implementation, we can use a mocking library like Moq:

// create a mock
var userServiceMock = new Mock<IUserService>();

// define behaviour for some (or all) methods
userServiceMock.Setup(usm => usm.GetCurrentUserName()).Returns("test user");

// do the replacement
services.Replace(ServiceDescriptor.Scoped(typeof(IUserService), _ => userServiceMock.Object));

If there is more than one implementation registered (e.g. multiple authorization handlers), we can’t just call Replace, because it only replaces the first implementation that it finds. Instead, we first have to call RemoveAll and then Add the desired implementation (e.g. AllowAnonymousAuthorizationHandler):

services.RemoveAll<IAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, AllowAnonymousAuthorizationHandler>();

We can of course add some implementations (e.g. when re-adding our custom authorization):

services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler1>();
services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler2>();

Putting it all together

So this is how ConfigureWebHost method in our TestApplicationFactory can look like in the end:

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureAppConfiguration((context, configurationBuilder) => { ... });

    builder.ConfigureServices((context, services) =>
    {
        var userServiceMock = new Mock<IUserService>();
        userServiceMock.Setup(usm => usm.GetCurrentUserName()).Returns("test user");
        services.Replace(ServiceDescriptor.Scoped(typeof(IUserService), _ => userServiceMock.Object));

        services.RemoveAll<IAuthorizationHandler>();
        services.AddSingleton<IAuthorizationHandler, AllowAnonymousAuthorizationHandler>();
    });

    if (configureServices is null) return;

    builder.ConfigureServices(configureServices);
}

And the configureServices delegate can look like:

var configureServices = services => 
{
    services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler1>();
    services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler2>();
}

We can then decide whether we want to use the factory with the default configuration:

var defaultFactory = new TestApplicationFactory<Startup>();

Or pass in our custom one:

var parametrisedFactory = new TestApplicationFactory<Startup>(configureServices);

Last modified on 2022-01-07