Testing ASP.NET Core web applications with WebApplicationFactory

6 minute read

ASP.NET Core is a very powerful framework to create web applications, GRPC services and generally any kind of service/daemon.

Such power comes at the cost of complexity with many components being explicitly or implicitly set up to create an execution pipeline where each component takes care of a specific aspect of it like authentication, authorization, caching, routing, URL generation and so on.

This complexity can make authoring tests for real world applications quite a daunting task. Luckily, Microsoft created the WebApplicationFactory to support end-to-end testing and can be configured so to make unit testing quite convenient.

The WebApplicationFactory can be found in the NuGet package Microsoft.AspNetCore.Mvc.Testing and, despite the name of the package, it can be used to test any kind of ASP.NET Core application, be it based on MVC, Razor Page, or even GRPC.

Basic application

The first example uses a minimalist web application to show the basics that we will use later to build more complex examples.

Let’s introduce a minimalist ASP.NET Core web application that we will use for a basic test scenario. The web application is composed by a Program and by a Startup class.

public class Program
{
    public static void Main(string[] args) =>
        CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
                webBuilder.UseStartup<Startup>()
            );
}

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("All was good!");
        });
    }
}

As you can see, in the Startup class we only configure the pipeline so that every request is responded to with a 200 OK response whose payload is some simple text.

You can achieve something as minimal as the example above by using the empty web application template available in the .NET SDK.

$ dotnet new web -o Web

Using dotnet run and pointing our browser to the default address (http://localhost:5000) will get us the message.

Now that we have a simple web application to test, let’s create a test project. We will use the NUnit template available in the .NET SDK.

$ dotnet new nunit -o Tests

After the project is created, we can add a reference to the web application.

$ dotnet add reference path/to/web/application

Finally, we add the Microsoft.AspNetCore.Mvc.Testing package:

$ dotnet add package Microsoft.AspNetCore.Mvc.Testing

The project comes with a simple unit test class. We will use it to test that our web application behaves as expected.

First, let’s make sure that when we make a request to the web application, we receive the expected text.

[TestFixture]
public class Tests
{
    [Test]
    public async Task ShouldReturnExpectedText()
    {
        // ARRANGE
        var factory = new WebApplicationFactory<Web.Startup>();
        HttpClient client = factory.CreateClient();

        // ACT
        string result = await client.GetStringAsync("/");

        // ASSERT
        Assert.That(result, Is.EqualTo("All was good!"));
    }
}

Let’s unpack the snippet above.

First, we create an instance of WebApplicationFactory. Specifically, we create an instance of it pointing at the Startup class we used to define our application in the previous section.

Then, we use the CreateClient() method of the factory. This will get us the instance of HttpClient that we will use to issue the HTTP requests.

The peculiarity of this HttpClient is that it doesn’t use the default implementation of HttpMessageHandler but a custom one that shortcircuits the requests to the instance of our application running in memory that was created by the application factory.

This approach allows us to write unit tests as if we were hitting a normal web application while hiding the complexity of its instantiation from the unit test itself.

It is to be noted that creating instances of WebApplicationFactory is quite expensive so it’s better to reuse those instances across multiple tests.

Here’s the same test fixture rewritten so that the WebApplicationFactory is instantiated once by leveraging NUnit’s tests lifecycle. To make the advantage more apparent, an additional unit test was added.

[TestFixture]
public class Tests
{
    private WebApplicationFactory<Web.Startup> _factory;
    private HttpClient _client;

    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
        _factory = new WebApplicationFactory<Web.Startup>();
    }

    [SetUp]
    public void Setup()
    {
        _client = _factory.CreateClient();
    }

    [Test]
    public async Task ShouldReturnExpectedText()
    {
        var result = await _client.GetStringAsync("/");

        Assert.That(result, Is.EqualTo("All was good!"));
    }

    [Test]
    public async Task Returns200()
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, "/");

        using var response = await _client.SendAsync(request);

        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    }
}

You can see the source code of this example here.

Controllers

Most ASP.NET Core web applications use MVC controllers to perform actions or coordinate business logic services to achieve the intended result.

Let’s add a simple controller that echoes whatever string is sent as part of the URL.

[ApiController]
[Route("[controller]")]
public class EchoController : ControllerBase
{
    [HttpGet("{str}")]
    public string Get(string str)
    {
        return str;
    }
}

Also, our Startup class will be different enough, so that we can map requests to controllers.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

When it comes to the unit tests, very little has changed.

[Test]
public async Task ShouldReturnSentText()
{
    const string payload = "HelloWorld";

    var result = await _client.GetStringAsync($"/echo/{payload}");

    Assert.That(result, Is.EqualTo(payload));
}

You can see the source code of this example here.

Dependencies

One of the advantages of the WebApplicationFactory is that it gives us a convenient way to customize the application setup without touching the application itself.

The most obvious case is with dependencies of the controllers. Dependencies like database connections or remote services SDKs should be replaced with fakes that help us asserting our expectations on the behavior of the application.

Let’s take the case of a controller that simply returns whatever is returned from a service.

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IWeatherForecastService _weatherForecastService;
    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(
        IWeatherForecastService weatherForecastService,
        ILogger<WeatherForecastController> logger
    )
    {
        _weatherForecastService = weatherForecastService;
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        _logger.LogInformation("Returning weather forecasts");
        return _weatherForecastService.Get();
    }
}

We can simply inject a fake implementation of IWeatherForecastService that returns a predictable result and test that our controller returns said result.

[TestFixture]
public class Tests
{
    [Test]
    public async Task ShouldReturnExpectedForecast()
    {
        var testWeatherForecast = new WeatherForecast
        {
            Date = DateTime.Today,
            Summary = "All good!",
            TemperatureC = 25
        };

        var factory = new WebApplicationFactory<Web.Startup>().WithWebHostBuilder(builder => 
        {
            var fake = new FakeWeatherForecastService(testWeatherForecast);

            builder.ConfigureServices(services => services.AddSingleton<IWeatherForecastService>(fake));
        });

        var client = factory.CreateClient();

        var result = await client.GetFromJsonAsync<WeatherForecast[]>("/WeatherForecast/");

        Assert.That(result[0], Is.EqualTo(testWeatherForecast));
    }

    private class FakeWeatherForecastService : IWeatherForecastService
    {
        private readonly WeatherForecast _forecast;

        public FakeWeatherForecastService(WeatherForecast forecast)
        {
            _forecast = forecast;
        }

        public IEnumerable<WeatherForecast> Get() => new [] { _forecast };
    }
}

It is important to notice that our test is totally unaware of the controller being dependent on ILogger<>. This is precisely the advantage of this tecnique: we get to ignore all the plumbing details that we would be otherwise forced to take into consideration in the arrange phase of our tests.

On the other hand, our test needed to deserialize the result of the HTTP request in order to validate the result. This aspect could annoy many but I personally this is as another advantage of using the WebApplicationFactory because it allows us to test the concrete outputs of the application. Whether or not this should be the concern of a unit test it can definitely be up for debating.

You can see the source code of this example here.

Recap

In this post we’ve explored how the WebApplicationFactory can help creating tests that are obvlivious of all the plumbing needed to start a web application written in ASP.NET Core.

In future posts, we’ll look at how to use WebApplicationFactory to test GRPC services and how to integrate it in unit tests based on Moq and AutoFixture.

All samples are available on GitHub.