Integration Testing Strategy
Relevant source files
The following files were used as context for generating this wiki page:
- BuildingBlocks/BuildingBlocks.Tests/TestBase.cs
- BuildingBlocks/BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs
- BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs
- Core/Scheduling/Scheduling.Domain.Tests/ApplicationTests/HandlerTests/CreatePatientCommandHandlerTests.cs
- claude.md
- Core/Scheduling/Scheduling.Domain.Tests/SchedulingValidatorTestBase.cs
Introduction
The integration testing strategy for this project is designed to provide high confidence in the system's correctness by verifying the interaction between application components, domain logic, and the persistence layer. The primary goal is to ensure that commands and queries are processed correctly through the MediatR pipeline, validated against business rules, and correctly persisted to the database.
The strategy utilizes a two-tier test base hierarchy, focusing on handler integration tests that leverage an in-memory SQLite database to simulate real-world persistence without the overhead of a full SQL Server instance. This ensures tests are fast, isolated, and repeatable while maintaining transaction-based isolation.
Sources: claude.md:44-51, BuildingBlocks.Tests/TestBase.cs:17-23
Test Infrastructure and Environment
The testing infrastructure relies on a standardized base class, TestBase<TContext>, which sets up a controlled environment for each test execution. This includes configuring the dependency injection container, initializing the database schema, and managing transaction lifecycles.
Database Isolation and Lifetime
Tests use SQLite in-memory as the database provider. To ensure test isolation, every test runs within a transaction that is explicitly started during TestInitialize and rolled back during TestCleanup. This prevents data from leaking between test cases without the performance cost of recreating the database for every single test.
The diagram shows the lifecycle of a single integration test, highlighting the use of transactions for isolation.
Sources: BuildingBlocks.Tests/TestBase.cs:46-82, claude.md:44-51
Service Registration
The TestBase class automates the registration of core building blocks and allows bounded contexts to register their specific services through the RegisterBoundedContextServices abstract method.
| Component | Registration Type | Description |
|---|---|---|
| Logging | AddLogging() | Required for internal event handlers and diagnostic tracing. |
| DbContext | UseSqlite(_connection) | Configures EF Core to use the active in-memory SQLite connection. |
| Unit of Work | EfCoreUnitOfWork<T> | Registers the real implementation (not a mock) to test persistence logic. |
| MediatR | Context Specific | Registered in the derived context test base. |
Sources: BuildingBlocks.Tests/TestBase.cs:57-73, BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs:22-35
Handler Integration Testing
Handler tests verify the full execution path of a Command or Query. This includes validation, domain logic execution within the Aggregate Root, and final persistence.
Command Execution Flow
When a command is sent via GetMediator().Send(command), it triggers a series of behaviors, most notably the TransactionBehavior. This behavior ensures that the handler execution is wrapped in a unit of work transaction.
This sequence illustrates how the TransactionBehavior manages the persistence lifecycle during an integration test.
Sources: BuildingBlocks.Tests/TransactionBehaviorTests.cs:33-46, BuildingBlocks.Tests/TestBase.cs:85-88, Core/Scheduling/Scheduling.Domain.Tests/ApplicationTests/HandlerTests/CreatePatientCommandHandlerTests.cs:15-30
Verifying Persistence
A critical part of the integration strategy is "reloading" entities from the database after a command completes to verify state changes. Tests use the Uow.RepositoryFor<T>().GetByIdAsync() method to fetch the record from the database and assert against its properties.
Transactional Integrity and Nested Scopes
The strategy includes rigorous testing of the EfCoreUnitOfWork to handle nested transactions, which occur when one command handler invokes another via Mediator.
Transaction Depth Management
The EfCoreUnitOfWork implementation uses a depth counter to ensure that only the outermost scope actually commits the transaction to the database. Integration tests verify that:
- Nested
BeginTransactionAsynccalls reuse the existing transaction. - Nested
CloseTransactionAsynccalls do not trigger a commit. - Only the final
Closeat depth 0 executes the commit strategy.
Sources: BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs:64-150, BuildingBlocks.Tests/TransactionBehaviorTests.cs:134-180
Commit vs Rollback Logic
The testing strategy validates that the pipeline correctly handles different outcomes:
- Success: Transaction commits.
- Business Exception: Transaction rolls back.
- Validation Exception: Transaction commits (allowing side effects of validation, such as logging, to persist if required by the design).
Sources: BuildingBlocks.Tests/TransactionBehaviorTests.cs:82-110
Validation Testing Strategy
While many tests are full integration tests, the project uses a specialized ValidatorTestBase for unit testing FluentValidation rules. These tests mock the IUnitOfWork and repositories to simulate entity existence checks (e.g., ensuring a patient exists before updating) without requiring a database.
| Helper Method | Purpose |
|---|---|
SetupPatientExists(id) | Configures mock repository to return true for existence checks. |
SetupUserRoles(roles) | Configures the ICurrentUser mock to simulate specific security contexts. |
RegexMatchValidation | Allows asserting against complex error messages with dynamic arguments. |
Sources: Core/Scheduling/Scheduling.Domain.Tests/SchedulingValidatorTestBase.cs:18-42, BuildingBlocks.Tests/TestBase.cs:100-106
Summary
The Integration Testing Strategy ensures robustness by combining real database persistence (via SQLite) with strict transactional boundaries. By utilizing the TestBase<TContext> and TransactionBehavior, the system guarantees that every command is atomic and its side effects are correctly verified. The use of depth-tracked unit of work transactions allows for complex, nested business flows to be tested reliably without data corruption or leaking state between tests.