Integration testing with WebApplicationFactory

ASP.NET Core · Practical · 14 min

Integration tests catch routing, binding, DI and serialization bugs that unit tests miss.

Microsoft.AspNetCore.Mvc.Testing provides WebApplicationFactory<TEntryPoint>, which boots your real app (its Program) on an in-memory TestServer. factory.CreateClient() returns an HttpClient wired to it: requests go through the full middleware pipeline, routing, binding and serialization, without opening a network port.

Setup:

  • Create an xUnit project, add Microsoft.AspNetCore.Mvc.Testing, and reference the web project.
  • The test project needs access to Program. If the compiler can't see it, add public partial class Program { } at the end of the app's Program.cs.
  • Use IClassFixture<WebApplicationFactory<Program>> to share one app instance across tests in a class.

Customize the app for tests with WithWebHostBuilder:

  • builder.UseEnvironment("Testing") and UseSetting("Key", "value") for configuration.
  • ConfigureTestServices(services => ...) to replace real dependencies (external APIs, clocks, email senders) with fakes. Remove the real registration or rely on the last registration winning.

For databases, prefer a real engine in a container (Testcontainers) or SQLite over the EF in-memory provider, which doesn't behave like a relational database.

Use System.Net.Http.Json helpers: GetFromJsonAsync<T>, PostAsJsonAsync, ReadFromJsonAsync<T>. Assert on status codes and response bodies, as an API client would.

Example

// Api.Tests/TodoApiTests.cs (xUnit + Microsoft.AspNetCore.Mvc.Testing)
using System.Net;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

public class TodoApiTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
    [Fact]
    public async Task Get_unknown_todo_returns_404()
    {
        var response = await factory.CreateClient().GetAsync("/todos/999");
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]   // IClock and FixedClock are defined in the app under test
    public async Task Greeting_uses_fake_clock()
    {
        var client = factory.WithWebHostBuilder(b => b.ConfigureTestServices(s =>
            s.AddSingleton<IClock>(new FixedClock(new DateTime(2026, 1, 1, 9, 0, 0))))).CreateClient();
        var text = await client.GetStringAsync("/hello/Ada");
        Assert.StartsWith("Good morning", text);
    }
}

Remember

  • WebApplicationFactory<Program> hosts the real app in memory
  • CreateClient gives an HttpClient through the full pipeline
  • ConfigureTestServices swaps real dependencies for fakes
  • Prefer a real database engine over the EF in-memory provider

Watch out for

  • Testing only handlers in isolation and missing binding, routing or serialization bugs.

Check yourself

  1. What does WebApplicationFactory.CreateClient() talk to?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/aspnet/core/test/integration-tests