Removes the USP_* stored procedures for Auth, Location, and Brewery data access in favour of services built with Dapper. Adds a new Features.Locations slice and optimistic concurrency support for the Brewery slice.
10 KiB
Testing
This document describes the testing strategy and how to run tests for The Biergarten App.
Overview
The project uses a multi-layered testing approach across backend and frontend:
- API.Specs - BDD integration tests using Reqnroll (Gherkin), run against a live, seeded database
- Features.*.Tests - One unit test project per backend feature slice
(
Features.Auth.Tests,Features.Breweries.Tests,Features.UserManagement.Tests,Features.Emails.Tests), covering that slice's command/query handlers and its own repository (mocked with Moq / DbMocker, no real database required) - Storybook Vitest project - Browser-based interaction tests for shared website stories
- Storybook Playwright suite - Browser checks against Storybook-rendered components
Running tests with Docker (recommended)
The easiest way to run all tests is using Docker Compose, which sets up an isolated test environment:
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up --abort-on-container-exit
This command:
- Starts a fresh SQL Server instance
- Runs database migrations
- Seeds test data
- Executes all test suites in parallel
- Exports results to
./test-results/ - Exits when tests complete
View test results
# List test result files
ls -la test-results/
# View specific test results
cat test-results/api-specs/results.trx
cat test-results/Features.Auth.Tests.trx
cat test-results/Features.Breweries.Tests.trx
cat test-results/Features.UserManagement.Tests.trx
cat test-results/Features.Emails.Tests.trx
Clean up
# Remove test containers and volumes
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
Running tests locally
You can run individual test projects locally without Docker:
Integration tests (API.Specs)
cd web/backend
dotnet test API/API.Specs/API.Specs.csproj
Requirements:
- SQL Server instance running
- Database migrated and seeded
- Environment variables set (DB connection, JWT secret)
Feature slice unit tests
Each feature slice has its own test project, covering its command/query handlers and repository:
cd web/backend
dotnet test Features/Features.Auth.Tests/Features.Auth.Tests.csproj
dotnet test Features/Features.Breweries.Tests/Features.Breweries.Tests.csproj
dotnet test Features/Features.UserManagement.Tests/Features.UserManagement.Tests.csproj
dotnet test Features/Features.Emails.Tests/Features.Emails.Tests.csproj
# Or run all of them at once via the solution:
for proj in Features/*.Tests; do dotnet test "$proj"; done
Requirements:
- No database required (handlers use Moq; repository tests use DbMocker to simulate SQL Server responses)
Frontend Storybook tests
cd web/frontend
npm install
npm run test:storybook
Purpose:
- Verifies shared stories such as form fields, submit buttons, navbar states, toasts, and the theme gallery
- Runs in browser mode via Vitest and Storybook integration
Frontend Playwright Storybook tests
cd web/frontend
npm install
npm run test:storybook:playwright
Requirements:
- Storybook dependencies installed
- Playwright browser dependencies installed
- The command will start or reuse the Storybook server defined in
playwright.storybook.config.ts
Test coverage
Current coverage
Features.Auth.Tests:
- User registration with validation
- User login with JWT token generation
- Password hashing and verification (Argon2id)
- Email confirmation and confirmation-email resend
- Refresh token exchange
- JWT token generation, validation, and claims handling
IAuthRepositorydata access (DbMocker-backed)- Invalid credentials and 404 error responses (via API.Specs)
Features.Breweries.Tests:
- Brewery create/update/delete commands and get-by-id/get-all queries
Features.UserManagement.Tests:
- User get-by-id/get-all queries and the (currently unrouted) update command
Features.Emails.Tests:
- Registration and resend-confirmation email dispatch handlers
Frontend UI Coverage:
- Shared submit button states
- Form field happy path and error presentation
- Navbar guest, authenticated, and mobile behavior
- Theme gallery rendering across Biergarten themes
- Toast interactions and themed notification display
Planned coverage
- Password reset functionality
- Beer post operations
- User follow/unfollow
- Image upload service
- Frontend route integration coverage beyond Storybook stories
Testing frameworks and tools
xUnit
- Primary unit testing framework
- Used for Repository and Service layer tests
- Supports parallel test execution
Reqnroll (Gherkin/BDD)
- Behavior-driven development framework
- Used for API integration tests
- Human-readable test scenarios in
.featurefiles
FluentAssertions
- Expressive assertion library
- Makes test assertions more readable
- Used across all test projects
Moq
- Mocking framework for .NET
- Used in Service layer tests
- Enables isolated unit testing
DbMocker
- Database mocking for repository tests
- Simulates SQL Server responses
- No real database required for unit tests
Test structure
API.Specs (integration tests)
API.Specs/
├── Features/
│ ├── Registration.feature # Registration scenarios
│ ├── Login.feature # Login scenarios
│ ├── Confirmation.feature # Email confirmation scenarios
│ ├── ResendConfirmation.feature # Resend-confirmation scenarios
│ ├── TokenRefresh.feature # Refresh token scenarios
│ ├── AccessTokenValidation.feature # Protected endpoint access scenarios
│ └── NotFound.feature # 404 handling
├── Steps/
│ ├── AuthSteps.cs # Step definitions for the Auth features
│ └── ApiGeneralSteps.cs # Shared/general step definitions
├── Mocks/
│ ├── MockEmailDispatcher.cs # Substitutes Features.Emails' IEmailDispatcher
│ └── MockEmailProvider.cs # Substitutes Infrastructure.Email's IEmailProvider
└── TestApiFactory.cs # Test server setup (swaps in the mocks above)
Example Feature:
Feature: User Registration
As a user
I want to register
So that I can access the platform
Scenario: Successful user registration
Given I have valid registration details
When I register a new account
Then I should receive a JWT token
And my account should be created
Features.Auth.Tests
Features.Auth.Tests/
├── Commands/
│ ├── RegisterUserHandlerTests.cs
│ ├── ConfirmUserHandlerTests.cs
│ ├── ResendConfirmationEmailHandlerTests.cs
│ └── RefreshTokenHandlerTests.cs
├── Queries/
│ └── LoginHandlerTests.cs
├── Repository/
│ └── AuthRepositoryTests.cs # DbMocker-backed repository tests
└── Services/
├── TokenServiceRefreshTests.cs
└── TokenServiceValidationTests.cs
Each of the other three slices (Features.Breweries.Tests,
Features.UserManagement.Tests, Features.Emails.Tests) follows the same
shape: a Commands//Queries/ folder with one test file per handler.
Writing tests
Unit test example (xUnit)
public class LoginHandlerTests
{
[Fact]
public async Task Handle_WithValidData_ReturnsPayloadWithMatchingUsername()
{
// Arrange
var authRepoMock = new Mock<IAuthRepository>();
var passwordInfraMock = new Mock<IPasswordInfrastructure>();
var tokenServiceMock = new Mock<ITokenService>();
var handler = new LoginHandler(authRepoMock.Object, passwordInfraMock.Object, tokenServiceMock.Object);
// ...set up mocks for a known user/credential...
// Act
var result = await handler.Handle(new LoginQuery("testuser", "password123"), CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.AccessToken.Should().NotBeNullOrEmpty();
}
}
Integration test example (Reqnroll)
Scenario: User login with valid credentials
Given a registered user with username "testuser"
When I POST to "/api/auth/login" with valid credentials
Then the response status should be 200
And the response should contain a JWT token
Continuous integration
Tests run automatically in CI/CD pipelines using the test Docker Compose configuration:
# CI/CD command
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml build
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up --abort-on-container-exit
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
Exit codes:
0- All tests passed- Non-zero - Test failures occurred
Frontend UI checks should also be included in CI for the active website workspace:
cd web/frontend
npm ci
npm run test:storybook
npm run test:storybook:playwright
Troubleshooting
Tests failing due to database connection
Ensure SQL Server is running and environment variables are set:
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml ps
Port conflicts
If port 1433 is in use, stop other SQL Server instances or modify the port in
docker-compose.test.yaml.
Stale test data
Clean up test database:
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
View container logs
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml logs <service-name>
Best practices
- Isolation: Each test should be independent and not rely on other tests
- Cleanup: Use fixtures and dispose patterns for resource cleanup
- Mocking: Mock external dependencies in unit tests
- Descriptive Names: Use clear, descriptive test method names
- Arrange-Act-Assert: Follow AAA pattern in unit tests
- Given-When-Then: Follow GWT pattern in BDD scenarios