ASP.NET Core Integration Tests using WebApplicationFactory
Integration testing evaluates an application's components at a
broader level, ensuring that database connections, routing, middleware,
and dependency injection function seamlessly together. In ASP.NET Core,
the WebApplicationFactory<TEntryPoint> class from the
Microsoft.AspNetCore.Mvc.Testing package enables you to
host an in-memory test server and execute HTTP requests against your API
without deploying it to an external environment. This guide explains how
to set up, configure, and execute integration tests in C# using
WebApplicationFactory.
1. Setting Up the Test Project
To get started, create a dedicated test project using your preferred test framework, such as xUnit:
dotnet new xunit -n MyApp.IntegrationTests
cd MyApp.IntegrationTestsAdd a project reference to the web application you intend to test:
dotnet add reference ../MyApp/MyApp.csprojInstall the integration testing NuGet package:
dotnet add package Microsoft.AspNetCore.Mvc.TestingExposing the Program Class
Modern ASP.NET Core projects use top-level statements where the
Program class is generated implicitly as an internal class.
To make it accessible to your test project, add the following line to
the end of your web application's Program.cs file:
public partial class Program { }Alternatively, expose internal types to your test project inside the
web application's .csproj file:
<ItemGroup>
<InternalsVisibleTo Include="MyApp.IntegrationTests" />
</ItemGroup>2. Writing a Basic Integration Test
Using WebApplicationFactory<Program>, you can spin
up the application in-memory and obtain an HttpClient
instance configured to communicate with the test server.
using System.Net;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace MyApp.IntegrationTests;
public class BasicEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public BasicEndpointTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Get_HealthCheck_ReturnsSuccessAndCorrectContentType()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/health");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain; charset=utf-8", response.Content.Headers.ContentType?.ToString());
}
}3. Customizing the WebApplicationFactory
Most real-world integration tests require overriding specific
dependencies, such as using an in-memory or test database, or replacing
third-party API clients with mock implementations. You can achieve this
by subclassing WebApplicationFactory<Program>.
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MyApp.Data;
namespace MyApp.IntegrationTests;
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove the application's default DbContext registration
var descriptor = services.SingleOrDefault(d =>
d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// Register an in-memory database for testing
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
// Seed test data if required
var sp = services.BuildServiceProvider();
using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureCreated();
SeedTestData(db);
});
}
private static void SeedTestData(ApplicationDbContext db)
{
if (!db.Users.Any())
{
db.Users.Add(new User { Id = 1, Name = "Test User", Email = "test@example.com" });
db.SaveChanges();
}
}
}4. Writing Tests with the Custom Factory
Once the custom factory is defined, implement tests that interact with endpoints, verify payload responses, and assert against database states:
using System.Net;
using System.Net.Http.Json;
using MyApp.Models;
using Xunit;
namespace MyApp.IntegrationTests;
public class UserEndpointTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
public UserEndpointTests(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetUserById_ReturnsUser_WhenUserExists()
{
// Act
var response = await _client.GetAsync("/api/users/1");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var user = await response.Content.ReadFromJsonAsync<UserDto>();
Assert.NotNull(user);
Assert.Equal("Test User", user.Name);
}
[Fact]
public async Task CreateUser_ReturnsCreated_WhenPayloadIsValid()
{
// Arrange
var newUser = new CreateUserRequest
{
Name = "Jane Doe",
Email = "jane@example.com"
};
// Act
var response = await _client.PostAsJsonAsync("/api/users", newUser);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var createdUser = await response.Content.ReadFromJsonAsync<UserDto>();
Assert.NotNull(createdUser);
Assert.Equal("Jane Doe", createdUser.Name);
Assert.NotNull(response.Headers.Location);
}
}5. Overriding Environment and Configuration
You can also override appsettings values or specify an environment
(such as Testing) directly within your factory:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["FeatureFlags:EnableBetaFeature"] = "true",
["ExternalService:ApiKey"] = "test-api-key"
});
});
}This structure ensures that tests execute in an isolated environment that closely mirrors production without relying on external infrastructure.