Now we have an integration test which runs against our application and swaps out the 3rd party dependency we are going to want to create a number of different tests to check various items and scenarios. Looking at the code you can see this will get pretty repeative very quickly and this in itself is a code smell. Remember that unit and integration test code should be looked after as much as production application code. This means if a code smell is starting to appear then refactoring of the tests is the next step.
Original Integration Test
Let’s take a look at our current setup and see how we can change it.
[Fact]
public async Task Test3()
{
// Arrange
var hostBuilder = new HostBuilder()
.ConfigureWebHost(webHost =>
{
// Add TestServer
webHost.UseTestServer();
webHost.UseStartup<WebApplication41.Startup>();
// configure the services after the startup has been called.
webHost.ConfigureTestServices(services =>
{
// register the test one specifically
services.SwapTransient<IMySimpleGithubClient, MockSimpleGithubClient>();
});
});
// Build and start the IHost
var host = await hostBuilder.StartAsync();
// Create an HttpClient to send requests to the TestServer
var client = host.GetTestClient();
// Act
var response = await client.GetAsync("/");
// Assert
var responseString = await response.Content.ReadAsStringAsync();
var item = System.Text.Json.JsonSerializer.Deserialize<GithubUser>(responseString);
item.Name.Should().Be("My Test User");
}
As we can see the “Arrange” section of the test is getting quite cumbersome to manage. So how can we solve this?
As part of Microsoft.AspNetCore.Mvc.Testing
it provides a WebApplicationFactory
which can be used for this.
What is a WebApplicationFactory<>?
The documentation site describes it perfectly - https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-3.0
Factory for bootstrapping an application in memory for functional end to end tests.
But what does this actually mean?
It allows for doing all the setup which we were manually doing with the HostBuilder
ourselves before.
Using the factory the Test
endpoint test now becomes …
[Fact]
public async Task BasicEndPointTest()
{
// Arrange
var factory = new WebApplicationFactory<WebApplication41.Startup>();
// Create an HttpClient which is setup for the test host
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/Home/Test");
// Assert
var responseString = await response.Content.ReadAsStringAsync();
responseString.Should().Be("This is a test");
}
As you can see from above this reduces the boiler plate code dramatically. Happy days!
However some of the eagle eyed of you will have noticed that we are using the Startup
class directly from our application but not swapping out the dependency. This is the next step.
Using the WebApplicationFactory
Looking through the api surface of the WebApplicationFactory
there is a way to swap out dependencies as before but it feels a bit clunky.
Let me explain.
var masterFactory = new WebApplicationFactory<WebApplication41.Startup>();
var factory = masterFactory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(
services =>
{
services.SwapTransient<IMySimpleGithubClient, MockSimpleGithubClient>();
});
});
First off you create a master factory for the application. Then using that you create a further factory with the specified swap. You have to access the IWebHostBuilder
and then the ConfigureTestServices
method on that. This isn’t too much different from what we originally but it works.
Extend the WebAppplicationFactory
Now I’m all about making life easier for developers. One of the most common excuses I’ve heard over the years why people don’t write tests is “too complicated” or “I have to write so much code”. The likelyhood is that the more code you have to write means your code is over complicated or doesn’t have ideal design.
So how can we improve this from a test setup perspective?
First off we need to make it easy to swap out the dependencies. To do this I created a simple factory class which inherited from WebApplicationFactory
. Once we have that it’s simple OOP and we can override the ConfigureWebHost
method.
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// swap out dependencies
});
}
}
Now we’ve got the above we can access the IServiceCollection
and swap out the dependencies as before.
To allow for a clean api I added 2 different varients on how to change the service registrations. The two options are either through the constructor or a specific Registration delegate which is run before the host is created.
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
public Action<IServiceCollection> Registrations { get; set; }
public CustomWebApplicationFactory() : this(null)
{
}
public CustomWebApplicationFactory(Action<IServiceCollection> registrations = null)
{
Registrations = registrations ?? (collection => { });
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
Registrations?.Invoke(services);
});
}
}
How to use the CustomWebApplicationFactory<>
As specified earlier there are two ways of changing the service collection registrations.
You can create the setup as before and then specify an Action
delegate to action the swaps before being further processed.
[Fact]
public async Task Test3()
{
// Arrange
var factory = new CustomWebApplicationFactory<WebApplication41.Startup>();
// setup the swaps
factory.Registrations = services =>
{
services.SwapTransient<IMySimpleGithubClient, MockSimpleGithubClient>();
};
// Create an HttpClient which is setup for the test host
var client = factory.CreateClient();
** snip for brevity **
}
Or the other way is pass in the Action
delegate through the constructor which on initial testing to me seems a cleaner option.
[Fact]
public async Task Test3_1()
{
// Arrange
var factory = new CustomWebApplicationFactory<WebApplication41.Startup>(services =>
{
// setup the swaps
services.SwapTransient<IMySimpleGithubClient, MockSimpleGithubClient>();
});
// Create an HttpClient which is setup for the test host
var client = factory.CreateClient();
** snip for brevity
}
Conclusion
In this post we have continued looking at integration tests primarily looking at removing the need for a lot of boiler plate code to allow for setup and running of the tests. This have been achieved by using the WebApplicationFactory<>
and a derived version for more advanced scenarios.
Any questions/comments then please contact me on Twitter @WestDiscGolf
Other Posts In This Series
Part 1 - Integration Testing with ASP.NET Core 3.1
Part 2 - Integration Testing with ASP.NET Core 3.1 - Testing Your Application
Part 3 - Integration Testing with ASP.NET Core 3.1 - Swapping a dependency
Part 4 - This post
Part 5 - Integration Testing with ASP.NET Core 3.1 - Swapping a Dependency with Moq
Part 6 - Integration Testing with ASP.NET Core 3.1 - Set Default Headers for All Clients