While Unit Tests are fast, they operate in an imaginary mocked universe. They cannot catch issues like broken Routing, failing JWT Middleware, or malformed JSON serialization. Integration Testing physically boots up your entire API server in memory, connects a synthetic HttpClient, and fires real network requests against it.
Microsoft provides the Microsoft.AspNetCore.Mvc.Testing package to facilitate this. It spins up an in-memory TestServer using your actual Program.cs.
dotnet add package Microsoft.AspNetCore.Mvc.Testing
We use WebApplicationFactory<Program> to boot the app, and generate an HttpClient that routes directly to the in-memory server.
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
using System.Net;
// IClassFixture ensures the server boots up ONCE for all tests in this class to save time
public class UsersIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public UsersIntegrationTests(WebApplicationFactory<Program> factory)
{
// Creates a client that points directly to the in-memory API
_client = factory.CreateClient();
}
[Fact]
public async Task GetAllUsers_ReturnsSuccessStatusCode_AndCorrectContentType()
{
// ACT: Fire a physical network request
var response = await _client.GetAsync("/api/users");
// ASSERT: Verify 200-299 Status Code
response.EnsureSuccessStatusCode();
// ASSERT: Verify it returned JSON, not XML or HTML
Assert.Equal("application/json; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
You cannot let your Integration Tests hit your real Production SQL Server! You must configure the WebApplicationFactory to intercept the EF Core DbContext and replace it with an In-Memory Database specifically for testing.
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 1. Find the existing SQL Server DbContext registration
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
// 2. Remove it
if (descriptor != null) services.Remove(descriptor);
// 3. Inject a safe In-Memory Database instead!
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("IntegrationTestDb");
});
});
}
}
Q: "Extensive Integration Testing seems to verify the same logic as Unit Testing but provides more real-world confidence. Should we abandon Unit Testing and exclusively write Integration Tests using WebApplicationFactory?"
Architect Answer: "No, this violates the 'Test Pyramid'. Integration Tests are 'Heavy'. Booting the in-memory server and running EF Core (even an In-Memory DB) takes hundreds of milliseconds per test. A suite of 5,000 unit tests runs in 3 seconds. A suite of 5,000 integration tests takes 15 minutes. This slows down the CI/CD pipeline and frustrates developers. Furthermore, when an Integration Test fails, diagnosing the cause is difficult because the entire pipeline (Auth, Routing, DB, Serilog) was involved. Unit Tests should comprise 80% of your suite to verify pure business logic instantly, while Integration Tests make up 20% to verify the components connect properly."