Integration Testing (ASP.NET)
Verifying components work together through the real framework โ WebApplicationFactory, test servers, swapping in test databases, and the isolation that keeps it all reliable.
Integration tests verify that components cooperate correctly through the real framework โ routing, middleware, serialization, business logic, and a real (test) database โ rather than in isolation. In ASP.NET Core, WebApplicationFactory boots the app in-process so tests send genuine HTTP requests. They're slower than unit tests but catch the wiring and persistence bugs that mocked unit tests structurally cannot.
An integration test verifies that multiple parts of the system work correctly when wired together โ controller, middleware, validation, business logic, and the database โ through the real framework. Unit tests prove a piece works in isolation; integration tests prove the pieces connect.
In ASP.NET Core, WebApplicationFactory boots your actual application in-process, with its real DI container, middleware pipeline, and routing. You get an HttpClient that sends genuine HTTP requests to your endpoints โ exercising serialization, model binding, filters, and the full request path that unit tests skip.
You keep the framework real but substitute volatile externals: swap the production database for an in-memory provider or (for higher fidelity) a real database in a throwaway container via Testcontainers. Seed known fixtures, run the request, then assert both the HTTP response and the resulting database state.
Integration tests are the middle of the test pyramid โ fewer than unit tests, more than E2E. They're slower (a server and DB spin up) and need careful isolation (a clean DB per test/class) to stay reliable, but they catch the configuration, serialization, and SQL bugs that mocked unit tests never see.
Key Concepts
Verifies multiple components working together through the real framework, not in isolation.
ASP.NET Core's in-process test host that boots the real app and gives you an HttpClient.
An in-memory server running the real pipeline so tests send genuine HTTP requests without a network.
Spins up real dependencies (Postgres, Redis) in disposable Docker containers for high-fidelity tests.
Replacing specific registered services (e.g. the DB) at test time while keeping the rest real.
A clean, independent database/state per test so tests don't leak into and break each other.
1// ASP.NET Core integration test โ boots the REAL app in-process.2public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>> {3 private readonly HttpClient _client;45 public OrdersApiTests(WebApplicationFactory<Program> factory) {6 _client = factory.WithWebHostBuilder(b => {7 b.ConfigureServices(services => {8 // swap the real DB for a test database9 services.RemoveAll<DbContextOptions<AppDb>>();10 services.AddDbContext<AppDb>(o => o.UseInMemoryDatabase("test"));11 // (better: Testcontainers with real Postgres for fidelity)12 });13 }).CreateClient();14 }1516 [Fact]17 public async Task Post_Order_Persists_And_Returns_201() {18 // Act โ a REAL HTTP request through routing, middleware, the controller19 var res = await _client.PostAsJsonAsync("/api/orders", new { items = new[] { 1 } });2021 // Assert โ the response AND the persisted state22 res.StatusCode.Should().Be(HttpStatusCode.Created);23 var saved = await res.Content.ReadFromJsonAsync<OrderDto>();24 saved!.Id.Should().NotBeEmpty();25 }26}
Integration tests close the confidence gap that unit tests leave โ they prove your endpoints, configuration, and database access actually work together. Knowing how to write them (test hosts, service overrides, isolation, Testcontainers) is essential for backend reliability and is a common topic for backend and platform interviews.
Common Pitfalls
1Green Unit Tests, Broken Endpoint
Every unit test passes, but in production a new endpoint returns 500. The cause: a misconfigured JSON serializer setting and a missing DI registration โ things no unit test touched because they all mocked the framework.
Unit tests deliberately bypass the framework (routing, model binding, serialization, DI wiring), so whole categories of bugs live in the gaps between components and never get exercised until production.
Add integration tests with WebApplicationFactory that send real HTTP requests through the actual pipeline against a test database. The misconfigured serializer and missing registration fail the integration test immediately, before deploy โ exactly the wiring unit tests can't cover.
Takeaway: Unit tests verify logic; integration tests verify wiring. The bugs that hide between components (serialization, DI, routing, SQL) only surface when you test through the real framework end-to-end.
2Flaky Tests From Shared Database State
Integration tests pass when run alone but fail randomly in CI. Tests run in parallel and share one database, so one test's data leaks into another's assertions, producing non-deterministic failures.
Without isolation, integration tests interfere through shared state. Order-dependent and parallel-execution flakiness erodes trust in the suite, and developers start ignoring red builds.
Isolate state per test: give each test class its own database (unique in-memory name or a fresh container), reset/seed within a transaction that rolls back, or use respawn to wipe between tests. Tests become deterministic regardless of order or parallelism.
Takeaway: Integration tests are only valuable if they're reliable. Enforce strict state isolation โ a clean database per test โ so parallel, order-independent runs stay deterministic and trustworthy.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.