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:
WebApplicationFactoryfacilitates testing of the entire application stack, simulating real-world behavior more accurately than testing individual components. - Flexible Input:
ClassDataprovides a wide range of test inputs, increasing the comprehensiveness and flexibility of testing. - Simplified Configuration:
WebApplicationFactorysimplifies the setup of in-memory test server and client, making tests easier to write and maintain. - Test Isolation: Each
WebApplicationFactoryinstance is isolated, enabling parallel testing without interference, improving reliability and performance. - Realistic Environment:
WebApplicationFactoryincludes actual startup and configuration code, increasing the likelihood of catching issues that unit tests might miss. - Dynamic Behavior Control:
ClassDataallows 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 đ

Join the conversation.