Testing
The Tech Strategy Tool has three test projects covering domain logic, API behavior, and data persistence. This page documents the testing conventions, patterns, and how to run tests.
Test Projects
Section titled “Test Projects”| Project | Type | Dependencies | Tests |
|---|---|---|---|
TechStrat.Core.Tests | Unit | None | ~164 tests |
TechStrat.Api.Tests | Integration | Docker (Testcontainers) | ~36 tests |
TechStrat.Infrastructure.Tests | Integration | Docker (Testcontainers) | ~37 tests |
Running Tests
Section titled “Running Tests”All tests
Section titled “All tests”dotnet test TechStrat.slnxUnit tests only (no Docker)
Section titled “Unit tests only (no Docker)”dotnet test tests/TechStrat.Core.TestsSpecific test project
Section titled “Specific test project”dotnet test tests/TechStrat.Api.Testsdotnet test tests/TechStrat.Infrastructure.TestsConventions
Section titled “Conventions”Framework and assertions
Section titled “Framework and assertions”- Framework: xUnit
- Assertions: FluentAssertions
result.Should().Be(expected);list.Should().ContainSingle();act.Should().Throw<InvalidOperationException>();Test class naming
Section titled “Test class naming”{Feature}Tests — e.g., EventProcessorCreateTests, EventStoreTests
Test method naming
Section titled “Test method naming”{Scenario}_description using descriptive snake_case-style naming:
[Fact]public void Creating_a_team_adds_it_to_the_document()
[Fact]public void Deleting_a_principle_removes_it_from_all_objectives()
[Fact]public void Updating_with_same_value_returns_no_change()Independence
Section titled “Independence”No test should depend on another test’s side effects. Each test creates its own state from scratch.
Unit Tests (Core.Tests)
Section titled “Unit Tests (Core.Tests)”Unit tests cover the event processor’s domain logic. They are fast, require no infrastructure, and form the bulk of the test suite.
Test data builders
Section titled “Test data builders”Use helper methods or builder classes that construct a processor with known state by feeding a sequence of events:
private static EventProcessor CreateProcessorWithTeam(out Guid teamId){ var processor = new EventProcessor(); teamId = Guid.NewGuid(); processor.ProcessEvent(new EventEnvelope { EventType = "create_entity", TargetType = "Team", ActorId = Guid.NewGuid(), Data = new Dictionary<string, string> { ["id"] = teamId.ToString(), ["name"] = "Test Team", ["color"] = "#3498db" } }); return processor;}This pattern avoids duplicated setup boilerplate across test classes.
What to test
Section titled “What to test”- Event processing: correct state changes, proper rejections
- Validation: required fields, field limits, entity existence
- Conflict detection:
lastSeenSequencehandling - No-op detection: same-value updates
- Cascade behavior: delete cascades, reference cleanup
- Event decomposition: derived commands producing correct events
- Notifications: correct notification types and entity IDs
Integration Tests (Api.Tests)
Section titled “Integration Tests (Api.Tests)”Integration tests verify API endpoints using WebApplicationFactory<Program> with a real PostgreSQL database via Testcontainers.
Test infrastructure
Section titled “Test infrastructure”TestWebApplicationFactory— configures the test host with a Testcontainers PostgreSQL instanceTestHelper— provides typed helpers for common operations
Authentication in tests
Section titled “Authentication in tests”HTTP-only Secure cookies do not round-trip in WebApplicationFactory’s internal pipeline. Instead, seed users and sessions directly in the database:
var client = await TestHelper.SeedUserAndLogin(factory, "editor", "editor", "editor");// client is now an authenticated HttpClientHelper methods
Section titled “Helper methods”TestHelper provides typed helpers that handle event submission with proper structure:
CreateTeam(client, name, color)— creates a team and returns the IDCreatePrinciple(client, teamId, title)— creates a principleCreateObjective(client, teamId, title)— creates an objectiveSubmitEvent(client, eventType, targetId, data)— submits any event
What to test
Section titled “What to test”- Endpoint routing and HTTP status codes
- Authorization policies (correct role requirements)
- Request/response serialization
- Event processing through the full pipeline
- CSRF header enforcement
- Error responses
Persistence Tests (Infrastructure.Tests)
Section titled “Persistence Tests (Infrastructure.Tests)”Persistence tests verify repository implementations against a real PostgreSQL database using Testcontainers with the postgres:17 image.
What to test
Section titled “What to test”- CRUD operations on all repositories
- Query behavior (filtering, ordering, pagination)
- Index effectiveness
- JSONB serialization/deserialization
- Cascade deletes
- Unique constraint enforcement
Package References
Section titled “Package References”Test project .csproj files use <PackageReference Include="..." /> without versions:
<PackageReference Include="xunit" /><PackageReference Include="FluentAssertions" /><PackageReference Include="Microsoft.NET.Test.Sdk" />Versions are managed centrally in Directory.Packages.props.
Debugging test failures
Section titled “Debugging test failures”- Check Docker — integration test failures often mean Docker is not running
- Run unit tests first — they are fast and catch domain logic issues without infrastructure noise
- Read assertion messages — FluentAssertions provides descriptive failure messages
- Check event rejections — if a state setup step fails, subsequent assertions will also fail. Check
ProcessingResult.StatusandRejectionReason
Writing new tests
Section titled “Writing new tests”- Identify the closest existing test class for your feature
- Follow the naming convention:
{Scenario}_description - Use builder methods to set up state
- Assert specific outcomes, not implementation details
- Keep tests focused — one logical assertion per test (FluentAssertions chains count as one)