Testing in .NET 6 with WebApplicationFactory (including Minimal APIs)
This article aims to discuss the benefits of integration testing in web applications and shows how the WebApplicationFactory class helps with this. At the end I also demonstrate how to use WebApplicationFactory to test the new .NET 6 minimal APIs.
Integration vs unit testing
Unit testing aims to test small pieces of functionality in isolation from the rest of the application. Unit tests typically aim to call one function on a class and assert that, what is returned from that function is what is expected. Unit testing a .NET Web API application typically consists of testing a single controller method as a function call and avoids having to call the endpoint itself over HTTP. Calling the running endpoint would involve setting up the application with all its dependencies, for example adding data access layer interfaces to the dependency injection container, adding references to databases, setting up authentication etc.
Integration testing by contrast aims to test the endpoint itself with all the dependencies in place. Integration test aims to bring together dependent components and test them as a whole. In a web application this would consist of calling the HTTP endpoint and getting a response.
Unit testing the controller
As mentioned above when unit testing, we want to isolate the test to one single piece of functionality. In a .NET web application, we can test function calls on the controller classes. For example, testing the Get method on the standard WeatherForecastController class:
As you can see, this is a simple function call to get the result. There is a dependency to the controller (ILogger) so we just pass in a mock of the dependency because we are not interested in testing the functionality of the logger just the controller itself.
Integration testing with TestServer
If we wanted to generate an integration test for the same functionality, we want to test the actual HTTP call itself. That would mean setting up a test server so that we can make a request and get a response. What would be needed is an in-memory web server that can accept our request and return a response. We would also need to setup any dependencies that the application depends on. Your standard Setup class may include numerous dependencies that get added to the application for example your Startup class may configure services like this:
This is where the TestServer class comes in. The TestServer class allows you to create an in-memory web server that responds to HTTP requests fora .NET web application. For example the following test is an integration test using the TestServer class to make a request to the WeatherForecast controller.
We build our web host here using the Startup class from our application. The problem is that our Startup class may contain stuff like a DbContext pointing to an actual database, we would want to replace this with a mocked in-memory database for our testing so that we don’t have to rely on a database server being present. This is handy if you want to run integration tests as part of an automated build pipelines. This is where WebApplicationFactory comes in.
How does WebAppplicationFactory help
WebApplicationFactory is a class dedicated to creating an in-memory application for use in integration and end-end testing.
We can create a class that inherits from WebApplicationFactory and it will handle bootstrapping TestServer and giving us back a HttpClient that can be used for making requests.
This allows us to do things like putting a mocked in-memory DbContext into our application, overriding the actual DbContext.
As you can see, inside our WeatherForecastApplication we are overriding the DbContext in our services to use an in-memory one. We can now use this new application to create a HttpClient for use in testing the endpoint.
This is a bit cleaner than the previous example that explicitly used the TestServer class and we don’t have to worry about creating a TestServer instance as this is handled by the WebApplicationFactory.
Unit testing with WebApplicationFactory and Minimal APIs
The new minimal APIs in .NET 6 are great if you want the least boiler plate code possible, but they have a limitation in that they dispense with the concept of a controller class. This means that unit testing can no longer be done through calling the function on a controller class. For a more in depth explanation of why minimal APIs are hard to unit test I would recommend watching this video here from Nick Chapsas
Scott Hanselman published an article explaining how you could do unit testing using the WebApplicationFactory for minimal APIs.
Here is what that looks like:
On line 12 we can see that we are making the class Program available to the test project. The WebApplicationFactory will use this class to setup your Web API.
Here is the class used for testing that inherits from WebApplicationFactory. As you can see we are setting its type to Program so the WebApplicationFactory knows how to setup the WebAPI
Any dependencies added to the services collection will override the ones setup in your main app. In this instance we are passing in a test repository for our IRepository interface. The test implementation will take precedence over the one setup in the Program class.
Our test then looks like this:
Scott Hanselman calls this a unit test in his article but as argued by Nick Chapsas in his video, this isn’t really a unit test as it includes all the setup of dependencies the WebApplicationFactory uses. The request goes through the HttpClient pipeline so your test isn’t as isolated as we would like in a pure unit test.
I would argue that this is a good compromise however and if you really want to use minimal APIs then this is a good solution.
In summary the WebApplicationFactory class allows you to create integration tests that utilise TestServer as an in-memory web server and this can also be used as a way to unit test minimal APIs.