DOTNET Contents

Integration Tests with WebApplicationFactory

WebApplicationFactory integration tests are where you catch real breakage: routing, DI, auth, serialization, middleware order, and database wiring. If you skip this, you will ship “works locally” incidents to production.

On this page

Production incident

A new middleware is added before UseAuthentication. Suddenly all endpoints behave differently. Unit tests pass because they bypass the pipeline. In production, auth headers are ignored and users get 401/403 storms. Another time, JSON serialization settings change and clients break. These failures only show up when you run the real application pipeline. That is what WebApplicationFactory is for.

Symptoms

  • Endpoints fail due to middleware order or DI wiring.
  • Model binding/validation behaves differently than expected.
  • Auth/authorization policies misbehave after refactors.
  • Serialization changes break clients silently.

Root causes

  • Testing only controllers/services directly, not the full pipeline.
  • Integration tests coupled to real infra (shared DB) causing flakiness.
  • No isolated configuration for tests; secrets and prod settings leak into test runs.

Diagnosis

# Find integration test harness usage
grep -R "WebApplicationFactory" -n test
grep -R "CreateClient" -n test
grep -R "ConfigureWebHost" -n test

Anti-pattern

  • Integration tests that hit real external services.
  • Tests that depend on execution order or shared state across tests.
  • Hardcoding ports and assuming environment variables.

Correct pattern

Boot the app in-memory, override configuration, and use a real HttpClient against the test server. Replace external dependencies with fakes or Testcontainers (next topic).

Minimal test server pattern

public class ApiFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Test");

        builder.ConfigureAppConfiguration((ctx, cfg) =>
        {
            // override config for tests if needed
        });

        builder.ConfigureServices(services =>
        {
            // Replace external HTTP clients, message buses, etc.
        });
    }
}

public class HealthTests : IClassFixture<ApiFactory>
{
    private readonly HttpClient _client;

    public HealthTests(ApiFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Ready_returns_200()
    {
        var resp = await _client.GetAsync("/health/ready");
        Assert.True(resp.IsSuccessStatusCode);
    }
}

What to assert (production-focused)

  • Routing + status codes + content types.
  • Auth: correct 401 vs 403 behavior.
  • Validation: bad input returns predictable 400 with problem details.
  • Headers: security headers, caching headers, correlation id echoes.
  • Serialization contracts: stable JSON shapes.

Security and performance impact

  • Security: pipeline tests catch missing auth middleware, broken policies, missing headers.
  • Performance: keep integration tests targeted. Too many heavy tests slow CI; use them where they catch real breakage.

Operational notes

  • CI: run integration tests on PRs for critical services.
  • Rollout: if a production incident was caused by middleware/config, write an integration test that reproduces it.
  • Rollback: integration tests protect you from shipping the same regression again during rollback/hotfix cycles.

Checklist

  • Integration tests boot the real pipeline with WebApplicationFactory.
  • External dependencies are replaced or containerized (no real internet calls).
  • Tests are isolated and deterministic.
  • Critical contracts (auth/validation/serialization) are asserted.
  • Incident regressions become tests.