SuperAPI AI Directory

dotnet-testing-advanced-aspnet-integration-testing

Testing & quality

View repository

Updated 10-16 06:22

About this skill

ASP.NET Core Integration Testing Guide

Core Concepts

Two Definitions of Integration Testing

  1. Multi-object collaboration testing — integrating two or more classes to test whether they work correctly together
  2. External resource integration testing — tests that use external resources such as databases, external services, files, etc.

Why do we need integration testing?

  • Ensure multiple modules work correctly after integration
  • Integration points that unit tests cannot cover: Routing, Middleware, Request/Response Pipeline
  • Confirm whether the WebApplication integration and configuration are correct
  • Confirm whether exception handling is complete

Positioning in the Test Pyramid

Test Type Test Scope Execution Speed Maintenance Cost Recommended Ratio
Unit Test Single class/method Very fast Low 70%
Integration Test Multiple components Moderate Moderate 20%
End-to-end Test Complete flow Slow High 10%

Principle 1: Use WebApplicationFactory to Create a Test Environment

Through WebApplicationFactory<Program> create an in-memory TestServer, paired with IClassFixture to share across test classes.

Basic Flow

  1. Test class implements IClassFixture<WebApplicationFactory<Program>>
  2. Inject the Factory through the constructor
  3. Use factory.CreateClient() to create an HttpClient
  4. Send requests to API endpoints and verify the responses

Custom Factory

Inherit WebApplicationFactory<Program> and override ConfigureWebHost:

  • Remove the original DbContextOptions, replace it with UseInMemoryDatabase
  • Use services.Replace() to replace external services with test versions
  • Configure builder.UseEnvironment("Testing") to use the test environment

Test Base Class

Create a IntegrationTestBase to encapsulate shared logic:

  • Factory and HttpClient initialization
  • SeedShipperAsync() and other data preparation methods
  • CleanupDatabaseAsync() cleanup methods
  • Implement IDisposable to ensure resource release

For complete code examples (basic usage, custom Factory, test base class), please refer to references/webapplicationfactory-examples.md

Principle 2: Use AwesomeAssertions.Web to Verify HTTP Responses

Provides fluent HTTP status code assertions and strongly typed response verification:

  • Status code assertions:Be200Ok()、Be201Created()、Be400BadRequest()、Be404NotFound() etc.
  • Satisfy verification:response.Should().Be200Ok().And.Satisfy<T>(result => { ... })
  • Advantages: replaces manual ReadAsStringAsync() + JsonSerializer.Deserialize<T>() verbose code

Principle 3: Use System.Net.Http.Json to Simplify JSON Operations

  • PostAsJsonAsync(url, object) — replaces manual JsonSerializer.Serialize + new StringContent
  • ReadFromJsonAsync<T>() — replaces manual ReadAsStringAsync + JsonSerializer.Deserialize

For complete assertion and JSON operation examples, please refer to references/assertion-and-json-examples.md

Three Levels of Integration Testing Strategy

Level Features Testing Focus Factory Configuration
1 No database, no Service API input/output, routing, status codes Use the original Factory directly
2 Has Service (NSubstitute) Dependency injection configuration, service interaction ConfigureTestServices
3 Full architecture + database Real CRUD, data integrity InMemoryDatabase replacement

Level 1: Simple WebApi project

The simplest form, directly use WebApplicationFactory<Program> to test the input and output of each API endpoint.

Level 2: WebApi project depending on a Service

Use NSubstitute to create Service stubs, and through the custom Factory's ConfigureTestServices inject test services.

Level 3: Complete WebApi project

Includes real database operations, removes the original DbContextOptions and switches to InMemoryDatabase, calling EnsureCreated() in tests to create the structure.

For complete code examples of all three levels, please refer to references/three-level-testing-strategy.md

CRUD operation tests

Test coverage

Operation Test focus
GET Single resource query, collection query, non-existent resource returns 404
POST Successful creation returns 201, validation error returns 400
PUT Successful update, handling of non-existent resources
DELETE Successful deletion returns 204, non-existent resource returns 404

Test data management

  • Call CleanupDatabaseAsync() before each test starts to ensure a clean state
  • Use SeedShipperAsync() and other methods to prepare test data
  • Use Satisfy<T>() to verify the correctness of the response content

For complete CRUD operation test code, please refer to references/crud-test-examples.md

Project structure recommendations

tests/
├── Sample.WebApplication.UnitTests/           # 單元測試
├── Sample.WebApplication.Integration.Tests/   # 整合測試
│   ├── Controllers/                           # 控制器整合測試
│   ├── Infrastructure/                        # 測試基礎設施
│   │   └── CustomWebApplicationFactory.cs
│   ├── IntegrationTestBase.cs                 # 測試基底類別
│   └── GlobalUsings.cs
└── Sample.WebApplication.E2ETests/            # 端對端測試

Common troubleshooting

'ObjectAssertions' 未包含 'Be200Ok' 的定義

This error is usually caused by installing an incompatible combination of assertion packages. Please confirm that the base assertion library and Web extension package versions are consistent.

Program class cannot be accessed

Ensure the main project's Program.cs contains the following declaration so the test project can access it:

public partial class Program { }

Test data interfering with each other

  • Clean the database before each test starts
  • Do not depend on the execution order of other tests
  • Use unique identifiers to avoid data conflicts

Package compatibility

Base assertion library Correct package
FluentAssertions < 8.0.0 FluentAssertions.Web
FluentAssertions >= 8.0.0 FluentAssertions.Web.v8
AwesomeAssertions >= 8.0.0 AwesomeAssertions.Web
<!-- 推薦組合 -->
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />

Best practices

Should do

  1. Independent test project — Separate integration tests from unit tests
  2. Test data isolation — Each test case has independent data preparation and cleanup
  3. Use a base class — Put shared setup and helper methods in a base class
  4. Clear naming — Use three-part naming (Method_Scenario_Expected)
  5. Appropriate test scope — Focus on integration points, do not over-test

Should avoid

  1. Mixing test types — Do not put unit tests and integration tests in the same project
  2. Test dependencies — Each test should be independent and not depend on the execution order of other tests
  3. Over-mocking — Integration tests should use real components as much as possible
  4. Ignoring cleanup — Test data must be cleaned up after the test is complete
  5. Hardcoded data — Use factory methods or the Builder pattern to create test data

Output format

  • Generate CustomWebApplicationFactory.cs, configuring the test DI container and database replacement
  • Generate IntegrationTestBase.cs test base class, including data preparation and cleanup methods
  • Generate controller test class (*ControllerTests.cs), covering CRUD operation verification
  • Modify test project .csproj, add Microsoft.AspNetCore.Mvc.Testing and AwesomeAssertions.Web
  • Ensure the main project Program.cs includes public partial class Program { } to support test access

Reference resources

Original article

Official documentation

Related skills

  • unit-test-fundamentals - Unit testing basics
  • nsubstitute-mocking - Mocking with NSubstitute
  • awesome-assertions-guide - AwesomeAssertions fluent assertions
  • testcontainers-database - Containerized database testing with Testcontainers