Enhance your .NET Testing #1: WebApplicationFactory
The WebApplicationFactory
class in .NET provides a powerful tool for integration testing. It allows you to create a factory for bootstrapping an application in memory for testing.
This article provides an overview of how to use WebApplicationFactory
to create end-to-end tests using the xUnit ClassData attribute.
What is WebApplicationFactory ?
Before going any further, let's find out what WebApplicationFactory
is used for.
Factory for bootstrapping an application in memory for functional end to end tests.
– Microsoft documentation
WebApplicationFactory
is used to create an instance of your web application in memory. This is beneficial for end to end tests, as it allows you to exercise the full stack of your application, from the HTTP request all the way down to your database or external services.
One of its great advantages is that it's already in the ASP.NET Core package, so there's no need to install a third-party package to use this feature. The complete namespace for it is Microsoft.AspNetCore.Mvc.Testing.
This means that developments are maintained by the dotnet community and are unlikely to become obsolete in the next few years.
Creating an instance of the factory class is as simple as this:
private readonly WebApplicationFactory<Startup> factory = new WebApplicationFactory<Startup>();
However, starting from .NET 6, the Startup
class has essentially merged into the Program
class, which is automatically generated from the top-level statements in the Program.cs
file.
Since WebApplicationFactory
use the Program.cs
, it will load the same configuration used to start the application. This way, there's no need to duplicate the appsettings.json
file in the test project.
However, as this is a web application, it is possible to override the configuration. In the case of tests, we often create fakes to remove the complexity of a class or dependency on an external component. It can also happen that the application configuration contains elements that refer to components we don't want to keep (such as an external link to a Redis cache or another API).
The ConfigureWebHost
method is used to override the configuration.
internal class GoatWebApplication : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Environment.SetEnvironmentVariable("CacheSettings:UseCache", "false");
_ = builder.ConfigureTestServices(services =>
{
services.AddTransient<Interface, ImplementationFake>();
});
}
}
A simple test could look like this:
[Fact]
public async Task ShoudReturns200_WhenGetValidPath()
{
// Arrange
await using var application = new GoatWebApplication();
var client = application.CreateClient();
// Act
var response = await client.GetAsync("/validpath");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
In this example, we're creating an instance of a test application that extends WebApplicationFactory<Program>
. We're then using this to create a client and send a GET request to the "/validpath" endpoint. The test asserts that the HTTP response status code is OK (200)
This first use of WebApplicationFactory
is interesting, and we can go further to make our tests data-driven.
Combine it with xUnit attributes
By combining WebApplicationFactory
and the xUnit ClassData
attribute, you can create robust end-to-end tests that cover a wide range of scenarios. You can use WebApplicationFactory
to create an instance of your application and simulate HTTP requests, and then use ClassData
to provide different sets of data for your tests.
This enables you to test how your application handles different inputs and scenarios, improving the quality of your tests and helping to catch any potential bugs or issues.
Before starting, if you need to refresh your memory, I suggest you take a quick look at the xUnit attributes
Here's an example of how you might do this:
internal class GoatWebApplication : WebApplicationFactory<Program>, IEnumerable<object[]>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Environment.SetEnvironmentVariable("CacheSettings:UseCache", "false");
_ = builder.ConfigureTestServices(services =>
{
services.AddScoped<Interface, ImplementationFake>();
});
}
public IEnumerator<object[]> GetEnumerator()
{
var client = CreateClient();
yield return new object[]
{
client,
"validpath"
200,
};
yield return new object[]
{
client,
"invalidpath"
404,
};
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
GoatWebApplication
now also inherits from IEnumerable<object[]>
to conform to ClassData
.
The enumerator will return 2 use cases:
- One with a valid path returning an HTTP 200 code
- The other with an invalid path returning an HTTP 404 code
As expected, the test is much smaller, with very little internal logic. It simply makes the call and implements the validation defined in the ClassData
[Theory]
[ClassData(typeof(LegacyV1TestWebApplication))]
public async Task ShoudReturnsExpectedHttpCode_WhenGetPath(HttpClient client, string path, int expectedStatusCode)
{
// Arrange
// Act
var response = await client.GetAsync(path);
// Assert
Assert.Equal(expectedStatusCode, response.StatusCode);
}
Summary
Using WebApplicationFactory
with xUnit's ClassData
attribute offers key advantages for end-to-end testing in .NET:
- Full Stack Testing:
WebApplicationFactory
facilitates testing of the entire application stack, simulating real-world behavior more accurately than testing individual components. - Flexible Input:
ClassData
provides a wide range of test inputs, increasing the comprehensiveness and flexibility of testing. - Simplified Configuration:
WebApplicationFactory
simplifies the setup of in-memory test server and client, making tests easier to write and maintain. - Test Isolation: Each
WebApplicationFactory
instance is isolated, enabling parallel testing without interference, improving reliability and performance. - Realistic Environment:
WebApplicationFactory
includes actual startup and configuration code, increasing the likelihood of catching issues that unit tests might miss. - Dynamic Behavior Control:
ClassData
allows for dynamic control over test behavior, making it easier to test varying scenarios.
These benefits contribute to more robust and reliable end-to-end tests for .NET applications.
Next article:
To go further, you can read the Microsoft documentation :
Have a goat day 🐐