ASP.NET Core cookbook · Intermediate

Write integration tests with WebApplicationFactory

Unit tests pass but routing, auth, validation and serialization bugs reach production.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Use WebApplicationFactory<Program> to run the real app in memory, replace external dependencies with ConfigureTestServices, and call endpoints with the factory's HttpClient.

Solution

// Program.cs (the app under test)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IClock, SystemClock>();
var app = builder.Build();
app.MapGet("/api/greeting", (IClock clock) => new { message = clock.Now.Hour < 12 ? "Good morning" : "Good afternoon" });
app.Run();

public interface IClock { DateTimeOffset Now { get; } }
public class SystemClock : IClock { public DateTimeOffset Now => DateTimeOffset.UtcNow; }
public partial class Program;                       // makes Program visible to the test project
// tests/GreetingTests.cs
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

public class GreetingTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
    [Fact]
    public async Task Says_good_morning_before_noon()
    {
        var client = factory.WithWebHostBuilder(b => b.ConfigureTestServices(services =>
            services.AddSingleton<IClock>(new FixedClock(new DateTimeOffset(2026, 10, 26, 9, 0, 0, TimeSpan.Zero)))))
            .CreateClient();

        var body = await client.GetFromJsonAsync<Greeting>("/api/greeting", TestContext.Current.CancellationToken);

        Assert.Equal("Good morning", body!.Message);
    }

    record Greeting(string Message);
    class FixedClock(DateTimeOffset now) : IClock { public DateTimeOffset Now => now; }
}

Pitfalls

  • Forgetting public partial class Program, so the test project can't reference the entry point.
  • Replacing the database with EF Core's in-memory provider; use a real engine (Testcontainers) for data tests.
  • Sharing state between tests through a single factory without resetting data.

Variations

  • Subclass WebApplicationFactory<Program> to centralise test configuration and Testcontainers setup.
  • Add a test authentication handler to simulate users and roles.

Further reading: Integration tests

Reference: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests

More ASP.NET Core recipes

Was this page helpful?