Core Concepts
Two Definitions of Integration Testing
- Multi-object collaboration testing — integrating two or more classes to test whether they work correctly together
- 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
- Test class implements
IClassFixture<WebApplicationFactory<Program>> - Inject the Factory through the constructor
- Use
factory.CreateClient()to create an HttpClient - Send requests to API endpoints and verify the responses
Custom Factory
Inherit WebApplicationFactory<Program> and override ConfigureWebHost:
- Remove the original
DbContextOptions, replace it withUseInMemoryDatabase - 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 methodsCleanupDatabaseAsync()cleanup methods- Implement
IDisposableto 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 manualJsonSerializer.Serialize+new StringContentReadFromJsonAsync<T>()— replaces manualReadAsStringAsync+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
- Independent test project — Separate integration tests from unit tests
- Test data isolation — Each test case has independent data preparation and cleanup
- Use a base class — Put shared setup and helper methods in a base class
- Clear naming — Use three-part naming (Method_Scenario_Expected)
- Appropriate test scope — Focus on integration points, do not over-test
Should avoid
- Mixing test types — Do not put unit tests and integration tests in the same project
- Test dependencies — Each test should be independent and not depend on the execution order of other tests
- Over-mocking — Integration tests should use real components as much as possible
- Ignoring cleanup — Test data must be cleaned up after the test is complete
- 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.cstest base class, including data preparation and cleanup methods - Generate controller test class (
*ControllerTests.cs), covering CRUD operation verification - Modify test project
.csproj, addMicrosoft.AspNetCore.Mvc.TestingandAwesomeAssertions.Web - Ensure the main project
Program.csincludespublic partial class Program { }to support test access
Reference resources
Original article
- Day 19 - Introduction to Integration Testing: Basic Architecture and Application Scenarios
- Ironman competition article:https://ithelp.ithome.com.tw/articles/10376335
- Sample code:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day19
Official documentation
- ASP.NET Core integration testing documentation
- WebApplicationFactory usage guide
- AwesomeAssertions.Web GitHub
Related skills
unit-test-fundamentals- Unit testing basicsnsubstitute-mocking- Mocking with NSubstituteawesome-assertions-guide- AwesomeAssertions fluent assertionstestcontainers-database- Containerized database testing with Testcontainers