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¶
| 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¶
All tests¶
Docker required
Integration tests use Testcontainers to spin up PostgreSQL instances. Docker must be running.
Unit tests only (no Docker)¶
Specific test project¶
Conventions¶
Framework and assertions¶
- Framework: xUnit
- Assertions: FluentAssertions
result.Should().Be(expected);
list.Should().ContainSingle();
act.Should().Throw<InvalidOperationException>();
Test class naming¶
{Feature}Tests — e.g., EventProcessorCreateTests, EventStoreTests
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¶
No test should depend on another test's side effects. Each test creates its own state from scratch.
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¶
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_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¶
- 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)¶
Integration tests verify API endpoints using WebApplicationFactory<Program> with a real PostgreSQL database via Testcontainers.
Test infrastructure¶
TestWebApplicationFactory— configures the test host with a Testcontainers PostgreSQL instanceTestHelper— provides typed helpers for common operations
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 HttpClient
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
Create events require data[\"id\"]
All create event helpers include data["id"] with the new entity's GUID. This is not the TargetId (which is the parent entity). Missing this field causes a rejection.
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)¶
Persistence tests verify repository implementations against a real PostgreSQL database using Testcontainers with the postgres:17 image.
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¶
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.
Tips¶
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¶
- 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)