.NET: Integration Testing ASP.NET Core APIs with a Test Database

Unit tests are useful for isolated business logic, but they cannot prove that an ASP.NET Core API works correctly across routing, middleware, dependency injection, serialization, and database operations. Integration tests exercise these components together and help catch failures that isolated tests may miss.

Understand what an integration test verifies

An integration test can send an HTTP request to an in-memory test server and inspect the resulting response.

ASP.NET Core provides WebApplicationFactory through the Microsoft.AspNetCore.Mvc.Testing package. It can host the application in a test environment without requiring a public network endpoint.

Create a test project

A typical setup uses a separate test project with the required testing packages and a reference to the API project.

The test factory can customize application services for the test environment. For example, it can replace the production database configuration with a test database.

Use a separate database

A test database prevents tests from accidentally modifying production or developer data.

For relational behavior, a dedicated disposable database instance or container running the same database engine as production can provide realistic results. EF Core’s in-memory provider is convenient for some tests, but it does not reproduce all relational database behavior.

When using SQLite or another alternative provider, be aware that SQL translation, constraints, transaction behavior, and supported features may differ from the production database.

Exercise the API over HTTP

A basic integration test might look like this:

public class HealthEndpointTests
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public HealthEndpointTests(
        WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task HealthEndpoint_ReturnsSuccess()
    {
        var response = await _client.GetAsync("/health");

        Assert.True(response.IsSuccessStatusCode);
    }
}

This example assumes the application exposes a /health endpoint and that the test project uses xUnit. The application entry point may need to be made accessible to the test project, depending on how the API is structured.

The test verifies the HTTP behavior of the hosted application rather than calling a controller method directly.

Isolate test data

Tests should not depend on execution order or leave data that affects subsequent tests.

Common strategies include using a fresh database for a test collection, creating and removing records within each test, or wrapping suitable operations in transactions.

The right approach depends on the database engine and whether the application uses background jobs or external systems.

Test realistic scenarios

Important integration cases include:

  • Successful requests.
  • Validation failures.
  • Authentication and authorization.
  • Missing resources.
  • Database constraint violations.
  • Transaction boundaries.
  • Serialization and response contracts.

Keep external dependencies controlled

Integration tests do not need to connect to real payment providers, email services, or production APIs. Replace external dependencies with controlled test implementations where appropriate, while retaining the database and framework behavior that the test is intended to verify.

Conclusion

ASP.NET Core integration tests help verify that the application works across its major boundaries. A dedicated test database, HTTP-level requests, isolated test data, and realistic failure scenarios provide stronger confidence than controller unit tests alone.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *