A production-grade .NET solution demonstrating comprehensive test coverage and QA best practices — unit testing, integration testing, architecture enforcement, and load testing — across a layered Library Management API.
LibrarySystem/
│
├── 📁 src/
│ ├── LibrarySystem.API/ # ASP.NET Core Web API (Minimal API + Controllers)
│ │ ├── Controllers/ # LoansController, MembersController
│ │ ├── Program.cs # App entry point, DI, Minimal API endpoints
│ │ └── LibrarySystem.API.csproj
│ │
│ ├── LibrarySystem.Data/ # Data Access Layer (EF Core)
│ │ ├── Entities/ # Book, Member, Loan
│ │ ├── LibraryDbContext.cs # EF Core DbContext with Fluent API config
│ │ └── LibrarySystem.Data.csproj
│ │
│ └── LibrarySystem.Services/ # Business Logic Layer
│ ├── Interfaces/ # IBookService, ILoanService, IMemberService
│ ├── BookService.cs
│ ├── LoanService.cs
│ ├── MemberService.cs
│ └── LibrarySystem.Services.csproj
│
├── 📁 tests/
│ ├── LibrarySystem.UnitTests/ # ✅ Unit Tests
│ ├── LibrarySystem.IntegrationTests/ # ✅ Integration Tests
│ ├── LibrarySystem.ArchitectureTests/# ✅ Architecture Tests
│ └── LibrarySystem.LoadTests/ # ✅ Load / Performance Tests
│
└── LibrarySystem.slnx # Solution file
Isolated tests validating individual service methods against an EF Core InMemory database, ensuring business logic correctness without any external dependencies.
| Aspect | Detail |
|---|---|
| Project | LibrarySystem.UnitTests |
| Framework | xUnit |
| Assertions | Shouldly — fluent, readable assertion library |
| Mocking | Moq — available for interface mocking |
| Database | EF Core InMemory Provider — unique Guid database per test for full isolation |
| Coverage | Coverlet — code coverage collection |
| Test Count | 17 tests across 3 test classes |
Test Classes:
BookServiceUnitTests— GetAll, GetById, GetById_NotFoundLoanServiceUnitTests— Borrow/Return flows, expired membership, max loans, no stock, outstanding fines, overdue fines, double returnMemberServiceUnitTests— Registration, duplicate email detection
End-to-end HTTP pipeline tests using WebApplicationFactory<T> to spin up an in-memory test server, exercising the full request pipeline — routing, middleware, DI, and database — without a running server.
| Aspect | Detail |
|---|---|
| Project | LibrarySystem.IntegrationTests |
| Framework | xUnit + IClassFixture<WebApplicationFactory<Program>> |
| Assertions | Shouldly |
| Test Server | Microsoft.AspNetCore.Mvc.Testing (WebApplicationFactory) |
| Database | EF Core InMemory — seeded per test via DI scope |
| Coverage | Coverlet |
| Test Count | 10 tests across 3 test classes |
Test Classes:
BookEndpointsIntegrationTests—GET /api/books,GET /api/books/{id}(OK + NotFound)LoanEndpointsIntegrationTests—POST /api/loans(Borrow),PUT /api/loans/{id}/return(Return), NotFound, BadRequest (out-of-stock, already returned)MemberEndpointsIntegrationTests—POST /api/members(Register), BadRequest (duplicate email)
Automated enforcement of structural rules and dependency boundaries using reflection-based analysis, preventing architectural erosion over time.
| Aspect | Detail |
|---|---|
| Project | LibrarySystem.ArchitectureTests |
| Framework | xUnit |
| Assertions | Shouldly |
| Rule Engine | NetArchTest.Rules — fluent API for .NET architecture constraints |
| Coverage | Coverlet |
| Test Count | 6 tests |
Enforced Rules:
| # | Rule | Purpose |
|---|---|---|
| 1 | Data layer → ⊘ Services, API |
Domain layer must not depend on higher layers |
| 2 | Services layer → ⊘ API |
Services must not depend on the presentation layer |
| 3 | Interfaces start with "I" |
Naming convention enforcement |
| 4 | Service classes end with "Service" |
Naming convention enforcement |
| 5 | Controllers end with "Controller" |
Naming convention enforcement |
| 6 | Controllers inherit ControllerBase |
Structural inheritance verification |
HTTP-based load testing against a running instance of the API to validate throughput, latency, and stability under sustained concurrent traffic.
| Aspect | Detail |
|---|---|
| Project | LibrarySystem.LoadTests |
| Framework | NBomber + NBomber.Http |
| Execution | Standalone console application (dotnet run) |
| Scenario | book_catalog_load_test — sustained load on GET /api/books |
| Load Profile | 50 concurrent connections for 30 seconds with a 5-second warmup |
⚠️ Note: The API must be running locally onhttp://localhost:5280before executing load tests.
All automated tests (Unit, Integration, Architecture) achieve complete isolation without Docker or external services:
| Mechanism | Used By | Description |
|---|---|---|
| EF Core InMemory Database | Unit Tests | Each test creates a fresh Guid-named in-memory database, ensuring zero cross-test contamination. |
| WebApplicationFactory<Program> | Integration Tests | Creates an in-memory HTTP test server with a replaced DbContext. Each test explicitly calls EnsureDeleted() + EnsureCreated() for a clean slate. |
| NetArchTest Reflection | Architecture Tests | Pure reflection-based assembly scanning — no state or infrastructure required. |
| Standalone Process | Load Tests | Executes against a live API instance. No test containers — the API is expected to be running externally. |
No Docker Compose, Testcontainers, or external broker configurations are required to run the automated test suite.
- .NET 10.0 SDK or later
- A terminal / PowerShell / Git Bash
Execute all Unit, Integration, and Architecture tests across the entire solution:
dotnet testTarget a single test project by name:
# Unit Tests only
dotnet test LibrarySystem.UnitTests
# Integration Tests only
dotnet test LibrarySystem.IntegrationTests
# Architecture Tests only
dotnet test LibrarySystem.ArchitectureTestsUse --filter to execute specific tests:
# Run a single test by display name
dotnet test --filter "BorrowBookAsync_ShouldSucceedAndDecrementStock_WhenAllRulesPass"
# Run all tests in a specific class
dotnet test --filter "FullyQualifiedName~LoanServiceUnitTests"
# Run all Architecture tests
dotnet test --filter "FullyQualifiedName~ArchitectureTests"Generate a coverage report using Coverlet and visualize it with ReportGenerator:
# Step 1: Install ReportGenerator global tool (one-time)
dotnet tool install -g dotnet-reportgenerator-globaltool
# Step 2: Run tests with coverage collection (Cobertura format)
dotnet test --collect:"XPlat Code Coverage" --results-directory ./TestResults
# Step 3: Generate the HTML report
reportgenerator -reports:./TestResults/**/coverage.cobertura.xml -targetdir:./CoverageReport
# Step 4: Open the report
# Windows:
start ./CoverageReport/index.html
# macOS:
open ./CoverageReport/index.html
# Linux:
xdg-open ./CoverageReport/index.htmlStep 1: Start the API first:
dotnet run --project LibrarySystem.APIStep 2: In a separate terminal, run the load test:
dotnet run --project LibrarySystem.LoadTestsReports are generated in LibrarySystem.LoadTests/bin/Debug/net10.0/reports/.
| Category | Library | Version | Purpose |
|---|---|---|---|
| Test Framework | xUnit | 2.9.3 | Test runner and discovery |
| Assertions | Shouldly | 4.3.0 | Fluent, expressive assertions |
| Mocking | Moq | 4.20.72 | Interface/class mocking |
| In-Memory DB | EF Core InMemory | 10.0.8 | Database isolation for tests |
| Architecture Rules | NetArchTest.Rules | 1.3.2 | Assembly-level constraint testing |
| Load Testing | NBomber | 6.4.1 | HTTP load & performance testing |
| Code Coverage | Coverlet | 10.0.1 | Coverage data collection |
| Test Server | Mvc.Testing | 10.0.8 | WebApplicationFactory for integration tests |
┌─────────────────────────────────────────────────┐
│ LibrarySystem.API │
│ (Minimal API + Controllers) │
├─────────────────────────────────────────────────┤
│ LibrarySystem.Services │
│ (BookService / LoanService / MemberService) │
├─────────────────────────────────────────────────┤
│ LibrarySystem.Data │
│ (LibraryDbContext / EF Core Entities) │
└─────────────────────────────────────────────────┘
Dependency Direction: API → Services → Data
Architecture tests enforce this dependency direction at build time, preventing any layer from referencing a layer above it.
This project is licensed under the MIT License. Feel free to fork, adapt, and use it as a reference for testing strategies in your own .NET projects.
Built with ❤️ by Ahmed Anwer.