Skip to content

Testing Guide

Run and write tests across the full stack.

Test Layers

The project has three testing layers:

Layer Backend Frontend
Unit tests xUnit + Moq + AwesomeAssertions Vitest (jsdom)
Integration tests xUnit + WebApplicationFactory
E2E tests Playwright

Backend Unit Tests

Framework: xUnit with Moq for mocking and AwesomeAssertions for assertions.

Project: UnicornTrails.API.Tests.Unit

Tests cover commands, queries, validators, and domain logic. Use Arrange/Act/Assert pattern. Shared test infrastructure (builders, factories, fakes) lives in the UnicornTrails.API.Tests.Infrastructure project.

Patterns

// Arrange
var handler = new CreateMarkerCommandHandler(context, dateTimeProvider);
var command = new CreateMarkerCommand(request);

// Act
var result = await handler.Handle(command, CancellationToken.None);

// Assert
result.Title.Should().Be(request.Title);

See Dev Environment Setup for commands.

Backend Integration Tests

Framework: xUnit with WebApplicationFactory for in-memory hosting.

Project: UnicornTrails.API.Tests.Integration

Tests exercise the full API stack including middleware, auth, rate limiting, and database. The test configuration uses:

  • A dedicated appsettings.Testing.json with rate limiting disabled and test JWT keys
  • MinIO (via docker-compose) for object storage
  • Test Cloudflare Turnstile secret key
  • In-memory or test PostgreSQL database

Integration tests cover endpoint responses, authorization, validation, and error handling.

Frontend Unit Tests

Framework: Vitest with jsdom environment.

Directory: tests/unit/

Structure mirrors the source:

tests/unit/
├── api/        # API utility tests
├── hooks/      # Custom hook tests
├── setup.ts   # Global test setup
├── stores/     # Zustand store tests
└── utils/      # Utility function tests

Patterns

// Utility functions
import { dateFormat } from "src/utils/dateFormat";
// -> tests/unit/utils/dateFormat.test.ts

// Hooks: renderHook + act
const { result } = renderHook(() => useSomeHook());

// Components: render + screen + userEvent
render(<MyComponent />);
await user.click(screen.getByRole("button"));

The setup file mocks global dependencies: localStorage, i18next, matchMedia, and animation frame functions.

Commands

npm run test:unit              # Run all unit tests
npm run test:unit:watch        # Watch mode
npm run test:unit:coverage     # With coverage report

Frontend E2E Tests

Framework: Playwright with three projects:

Project Runs Auth State
auth-setup Login flow first Saves storage state
unauthenticated Auth-only specs None
chromium All other specs Uses saved user.json

Structure

tests/e2e/
├── features/{feature}/
│   ├── {feature}.spec.ts
│   └── {feature}.page.ts
├── shared/
│   ├── fixtures/        # Auth setup
│   ├── data.helper.ts   # Test data generators
│   ├── paths.helper.ts  # Asset paths
│   ├── selectors.ts     # Common selectors
│   └── timeouts.ts      # Timeout constants
├── assets/              # Test images
└── .reports/            # Output (gitignored)

Key Patterns

  • Self-cleaning tests: Every test creates, uses, then deletes entities via the UI
  • Test data naming: Must use E2E_{timestamp}_ prefix via createTestName()
  • Page Object pattern: Page objects return values, assertions live in specs
  • Selector priority: getByRole > getByLabel > getByText > getByTestId

Commands

npm run test:e2e                    # Run all E2E tests
npm run test:e2e:ui                 # UI mode (debugging)
npm run test:e2e:headed             # See browser window
npm run test:e2e -- --grep "delete" # Run matching tests

Prerequisites

  1. Backend running: dotnet run --project UnicornTrails.API
  2. Frontend running: npm run dev
  3. .env.test with E2E_BASE_URL, E2E_API_URL, E2E_USERNAME, E2E_PASSWORD

CI Test Execution

Tests run automatically in GitHub Actions:

Check Backend CI Frontend CI
Build dotnet build --warnaserror npm run build
Lint npm run lint
Type Check npm run type-check
Unit Tests dotnet test (+ coverage) npm run test:unit
Integration Tests dotnet test (+ coverage)
Vulnerability Scan dotnet list package --vulnerable npm audit --audit-level=high
Secret Scan Gitleaks (shared workflow) Gitleaks

See CI/CD for pipeline configuration.