Skip to content

Commit 4a81db3

Browse files
authored
Merge pull request #464 from nanotaboada/feat/web-application-integration-tests
test(integration): add `WebApplicationFactory` HTTP tests (#421)
2 parents 48897c4 + eedf3f5 commit 4a81db3

9 files changed

Lines changed: 631 additions & 8 deletions

File tree

.github/copilot-instructions.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ test/Dotnet.Samples.AspNetCore.WebApi.Tests/
5858

5959
### Test naming conventions
6060

61-
Tests live under `test/.../Unit/`. Two naming patterns, strictly by layer:
61+
Two naming patterns, strictly by layer:
6262

63-
| Layer | Pattern | Example |
64-
|----------------------|---------------------------------------------------------------|------------------------------------------------------------------|
65-
| Controller | `{HttpMethod}_{Resource}_{Condition}_Returns{Outcome}` | `Get_Players_Existing_ReturnsPlayers` |
66-
| Service / Validator | `{MethodName}_{StateUnderTest}_{ExpectedBehavior}` | `RetrieveAsync_CacheMiss_QueriesRepositoryAndCachesResult` |
63+
| Layer | Location | Pattern | Example |
64+
|----------------------|-----------------------|---------------------------------------------------------------|------------------------------------------------------------------|
65+
| Controller (unit) | `test/.../Unit/` | `{HttpMethod}_{Resource}_{Condition}_Returns{Outcome}` | `Get_Players_Existing_ReturnsPlayers` |
66+
| Service / Validator | `test/.../Unit/` | `{MethodName}_{StateUnderTest}_{ExpectedBehavior}` | `RetrieveAsync_CacheMiss_QueriesRepositoryAndCachesResult` |
67+
| HTTP integration | `test/.../Integration/` | `{HttpMethod}_{Resource}_{Condition}_Returns{Outcome}` | `Get_Players_Existing_Returns200Ok` |
6768

68-
Each pattern has exactly three underscore-delimited segments. Do not add a fourth segment.
69+
Each pattern has exactly three underscore-delimited segments where `{HttpMethod}_{Resource}` counts as the first segment. Do not add a fourth segment.
6970

7071
### FluentValidation rule sets
7172

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc
4444

4545
### Added
4646

47+
- Add `adr/0013-testing-strategy.md` documenting the decision to implement the full test pyramid as a deliberate educational choice (#421)
48+
- Add `test/.../Integration/PlayerWebApplicationTests.cs` with 14 HTTP-layer integration tests covering all player endpoints and `/health` via `WebApplicationFactory<Program>` backed by in-memory SQLite; includes `Utilities/TestAuthHandler.cs` to bypass `[Authorize]` on `GET /players/{id:Guid}`; expose `Program` to the test project via `public partial class Program {}` in `Program.cs`; add `Microsoft.AspNetCore.Mvc.Testing` to the test project (#421)
4749
- Add `test/.../Integration/PlayerRepositoryTests.cs` with 9 integration tests covering `Repository<T>` (`GetAllAsync`, `FindByIdAsync`, `RemoveAsync`) and `PlayerRepository` (`FindBySquadNumberAsync`, `SquadNumberExistsAsync`); all tests use `DatabaseFakes.MigrateAsync()` on in-memory SQLite and are tagged `[Trait("Category", "Integration")]` (#461)
4850
- Add `ValidateAsync_SquadNumberNegative_ReturnsValidationError` test to exercise the `GreaterThan(0)` rule with a negative value, which passes `NotEmpty()` but fails the greater-than rule (#427)
4951
- Add `ValidateAsync_FirstNameEmptyInUpdateRuleSet_ReturnsValidationError` test to verify the `"Update"` rule set enforces structural field validation (#427)

adr/0013-testing-strategy.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# 0013. Testing Strategy
2+
3+
Date: 2026-04-10
4+
5+
## Status
6+
7+
Accepted
8+
9+
## Context
10+
11+
The project is a learning reference for a REST API built with ASP.NET Core. Its primary goal is educational clarity — it must be immediately understandable to developers studying the stack, not just functional in production.
12+
13+
For a CRUD API of this size, a single suite of HTTP-layer integration tests (using `WebApplicationFactory<Program>` backed by an in-memory SQLite database) would cover the majority of meaningful risk. Such tests exercise the full request pipeline — routing, middleware, validation, serialization, and database interaction — with minimal setup.
14+
15+
The question was whether to stop there or to also add unit tests for each layer (controller, service, validator, repository) separately.
16+
17+
## Decision
18+
19+
The project implements the full test pyramid:
20+
21+
- **Unit tests** for each layer in isolation (controller, service, validator, repository), with dependencies replaced by Moq mocks or in-memory fakes.
22+
- **Repository integration tests** that run EF Core queries against in-memory SQLite via the full migration chain, validating query correctness and migration health as a side effect.
23+
- **HTTP integration tests** (`PlayerWebApplicationTests`) that send real HTTP requests through `WebApplicationFactory<Program>`, exercising the entire pipeline end-to-end.
24+
25+
Some overlap between layers is accepted deliberately.
26+
27+
## Rationale
28+
29+
The redundancy is the point. Having all test types in one place allows a reader to see exactly what each layer tests, what it isolates, and what it leaves to the layer above or below. The test suite is as much documentation as it is a safety net.
30+
31+
The repository integration tests in particular serve two purposes beyond what the HTTP tests provide: they validate individual EF Core queries in isolation (making failures easier to diagnose) and they run `MigrateAsync()` as a side effect, which acts as a canary for migration chain health.
32+
33+
## Consequences
34+
35+
### Positive
36+
37+
- Every layer of the architecture has dedicated tests, making failure diagnosis straightforward.
38+
- The test suite demonstrates the full range of testing patterns available in the .NET ecosystem.
39+
- Migration health is continuously validated as a side effect of the repository tests.
40+
41+
### Negative
42+
43+
- Test count is higher than strictly necessary for confidence.
44+
- Maintenance cost is proportionally higher — changes to shared fixtures or fakes may require updates across multiple test classes.
45+
46+
### Neutral
47+
48+
- The HTTP integration tests (`PlayerWebApplicationTests`) are the minimum viable test suite. All other test classes add depth, not breadth.
49+
- The `409 Conflict` branch on `POST /players` is unreachable via the HTTP pipeline: the `BeUniqueSquadNumber` rule in the `"Create"` validation rule set catches duplicates first and returns `400`. This path is covered by the controller unit test, where validation is mocked to pass.

adr/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This directory contains Architecture Decision Records (ADRs) for this project. A
1818
| [0010](0010-use-serilog-for-structured-logging.md) | Use Serilog for Structured Logging | Accepted |
1919
| [0011](0011-use-docker-for-containerization.md) | Use Docker for Containerization | Accepted |
2020
| [0012](0012-use-stadium-themed-semantic-versioning.md) | Use Stadium-Themed Semantic Versioning | Accepted |
21+
| [0013](0013-testing-strategy.md) | Testing Strategy | Accepted |
2122

2223
## When to Create an ADR
2324

src/Dotnet.Samples.AspNetCore.WebApi/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,8 @@
122122
app.MapControllers();
123123

124124
await app.RunAsync();
125+
126+
public partial class Program
127+
{
128+
protected Program() { }
129+
}

test/Dotnet.Samples.AspNetCore.WebApi.Tests/Dotnet.Samples.AspNetCore.WebApi.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup Label="Test dependencies">
12+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" PrivateAssets="all" />
1213
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" PrivateAssets="all" />
1314
<PackageReference Include="Moq" Version="4.20.72" PrivateAssets="all" />
1415
<PackageReference Include="FluentAssertions" Version="8.9.0" PrivateAssets="all" />

0 commit comments

Comments
 (0)