Integration Testing with .NET WebApplicationFactory
Table of contents
Context
In this post I will cover my approach to writing integration tests, using .NETs WebApplicationFactory.
Why this approach?
After an initial setup, it's very straightforward writing new tests and refactoring old ones.
Easy to work with atomic data.
Works great with an in-memory database, so perfect if you love some TDD, or during refactorings that impact your domain model.
Debugging is easy.
Minimizes the need to inject SUTs with mocks or fakes, since you can use whatever you need from the Factory Services.
As every decision is a trade-off, disadvantages would include the fact that it takes some time setting up new test projects and your tests will not be the fastest possible.
For this approach, we will need the following technologies:
A .NET (Core) software application
xUnit test framework
Refit library for API tests
Setup
First thing to do is to create a class, inheriting from WebApplicationFactory<Startup>. Use this class to override the ConfigureWebHost(IWebHostBuilder builder)-method to configure the setup of your TestProject(s). I called this class the IntegrationTestFactory, as it will be used for integration testing.
As this is pretty much the Registry of your test project, you configure it for the needs of your test cases. For example:
Some configurations need to differ slightly from production? No problem as you can setup per your requirements. In my application, I'm using a SQL-DB, but for testing, I opt for an in-memory database.
Configurations need to be exactly the same as your production-setup? Easy, just recycle methods from your Registry: Cookies, Repos, Swagger, CORS, and so on.
Next Class we're making is a base class for test classes, let's call it ApplicationTestBase in this example:
The purpose of this class is to act as a Dependency Injection container, to keep actual testclasses nice and clean. All we do here is make available what will be needed during testing, using the GetRequiredService-method on the ServiceProvider. For example:
Now that the initial setup is ready, let me show some examples of how to use this approach. Note that you may need to come back and expand your internal fields and constructor depending on your test cases.
Unit Testing
For unit testing, no extra setup or inheritance is needed. Just create your test class and write your tests.
Integration Testing
Suppose your application supports users asking for a new password to be sent by mail. In my case, my controller would receive a request, trigger a handler with logic, and return a response for this specific request. Integration testing of this flow would focus on testing this handler containing the logic.
Setup for this type of test is as follows: We inherit from our ApplicationTestBase, declare the subject under test and assign its value, made available in the base class:
In this example, we inject the _dbContext and _mailService to accommodate the handler's constructor. The handler only has one method: Handle(IRequest request, CancellationToken token) and can be tested as follows:
API Testing
In this approach, for API tests, I added some extra setup, again with the idea of writing clean test classes.
For each controller in my API, I have an interface with all the methods my controller exposes. So there's my AccountController and its IAccountAPI, having a method with the following signature: Response RegisterAsync(RegisterRequest request)
.
The methods in the Interface are decorated with a Refit attribute, based on the HTTP methods and their relative path:
For the test class, we again inherit from a Testbase, in this case, one specifically made for API tests. Setup of this class is exactly the same as the ApplicationTestBase, but this has some methods interacting with the DB, specifically for the SUT & setting up atomic data for it.
Using Refit and our interface, we can very easily set up a mock of the controller with RestService.For<>() as shown below:
And write tests like this:
That's it.
Depending on your testing strategies, you can of course expand or finetune this setup to your liking.
Recap
Prerequisites & used technologies: .NET Web API, C#, xUnit, Refit
Setup IntegrationTestFactory, inheriting from WebApplicationFactory
Make test classes inherit from IntegrationTestFactory
...
Profit
Hope you enjoyed reading this article. Any feedback is highly appreciated in the comments below. Thank you for reading. And a thank you to Mister Magnificent for proof-reading!
Subscribe to my newsletter
Read articles from Mister P directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by