Files
the-biergarten-app/docs/website/testing.md

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

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:

  1. Starts a fresh SQL Server instance
  2. Runs database migrations
  3. Seeds test data
  4. Executes all test suites in parallel
  5. Exports results to ./test-results/
  6. 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
  • IAuthRepository data 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 & 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 .feature files

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

  1. Isolation: Each test should be independent and not rely on other tests
  2. Cleanup: Use fixtures and dispose patterns for resource cleanup
  3. Mocking: Mock external dependencies in unit tests
  4. Descriptive Names: Use clear, descriptive test method names
  5. Arrange-Act-Assert: Follow AAA pattern in unit tests
  6. Given-When-Then: Follow GWT pattern in BDD scenarios