Skip to Content
⚠️Active Development Notice: TimeTiles is under active development. Information may be placeholder content or not up-to-date.
DevelopmentContributingTesting Guidelines

Testing Guidelines

Use unit tests for isolated logic, integration tests for database-backed behavior, and E2E tests for user journeys. Unit tests may mock dependencies; integration tests exercise real PostgreSQL and Payload.

Test Types

TypeLocationFrameworkSpeedWhat to test
Unittests/unit/Vitest< 100msPure functions, validation, parsing, formatting
Integrationtests/integration/Vitest + PostgreSQLsecondsAPI endpoints, job processing, access control
E2Etests/e2e/Playwright10-30sComplete user workflows in the browser

Running Tests

# From project root make test # All tests make test-ai # AI-friendly JSON output make test-ai FILTER=date.test # Single file (24-120x faster) make test-ai FILTER=tests/unit # Directory make test-e2e # Playwright E2E tests # From apps/web pnpm test # All tests pnpm test:unit # Unit only pnpm test:integration # Integration only pnpm test:e2e # E2E tests

Results are saved as timestamped JSON in apps/web/.test-results/.

Mocking Rules

Integration Tests

  • Database operations — use test database
  • Payload CMS — use real Payload
  • Internal services — use actual implementations
  • File system — use temp directories
  • Job queues — use real handlers

External-service fakes are narrow, documented exceptions, not the default. For example, apps/web/tests/integration/services/all-transforms-pipeline.test.ts substitutes a geocoder to exercise transforms without provider credentials. Keep provider integration covered separately.

Unit Tests

Mock dependencies when needed to isolate the behavior under test. Prefer real pure helpers and temporary files where practical. Examples of controlled test substitutes include:

  • External paid APIs (Google Maps) — costs and rate limits
  • Rate-limited services — avoid CI quotas
  • Network failures — test error handling
  • Time — vi.setSystemTime() for date-dependent tests

Integration Test Setup

Use createIntegrationTestEnvironment() for tests that need a real database:

import { afterAll, beforeAll, beforeEach, describe } from "vitest"; import { createIntegrationTestEnvironment, IMPORT_PIPELINE_COLLECTIONS_TO_RESET, } from "@/tests/setup/integration/environment"; describe.sequential("Import workflow", () => { let testEnv: Awaited<ReturnType<typeof createIntegrationTestEnvironment>>; beforeAll(async () => { testEnv = await createIntegrationTestEnvironment(); }); afterAll(async () => { await testEnv.cleanup(); }); beforeEach(async () => { await testEnv.seedManager.truncate(IMPORT_PIPELINE_COLLECTIONS_TO_RESET); }); // Add tests using the real database and Payload here. });

Integration databases are isolated per worker, but multiple files share a worker process. Use sequential suites and explicit cleanup scope; never close the shared Payload connection. apps/web/vitest.config.ts declares the separately isolated project and retry policy.

Worker databases use the fork’s process ID and remain available after a run for debugging. The next integration global setup prunes leftover worker databases without open connections; it does not terminate active connections to clean them up. The template database is retained and reused when its schema is valid. Do not rely on a worker database surviving the next test run.

E2E has a different model: Playwright workers share one server and database per run. Use distinct test data and the fixtures in apps/web/tests/e2e/fixtures/; do not assume worker-level database isolation.

Unit Test Setup

Use factories and mocks for business logic:

import { vi } from "vitest"; import { createEvent } from "@/tests/setup/factories"; const mockPayload = { findByID: vi.fn(), create: vi.fn() }; it("validates event data", () => { const event = createEvent({ data: { title: "Test" } }); expect(validateEvent(event).valid).toBe(true); });

Test Credentials

Always use centralized test credentials to avoid hardcoded secrets:

import { TEST_CREDENTIALS, TEST_EMAILS } from "../constants/test-credentials"; const testUser = await payload.create({ collection: "users", data: { email: TEST_EMAILS.admin, password: TEST_CREDENTIALS.basic.password, role: "admin" }, });

Analyzing Failures

# Failed test names cat apps/web/.test-results/$(ls -t apps/web/.test-results/ | head -1) | jq '.testResults[] | select(.status=="failed") | .name' # Lint errors cat apps/web/.lint-results/$(ls -t apps/web/.lint-results/ | head -1) | jq '.[] | select(.errorCount > 0) | .filePath' # TypeScript errors cat apps/web/.typecheck-results/$(ls -t apps/web/.typecheck-results/ | head -1) | jq '.errors[] | {file, line, code, message}'
Last updated on