Toast
Contributor

Testing

Current testing patterns for services, route factories, integration tests, and coverage.

Toast’s testing style follows the architecture:

  • services are tested via factory injection
  • route factories are tested by mocking middleware and service deps
  • integration tests use the real PostgreSQL test database

Quick commands

pnpm test
pnpm --filter @toast/api test src/app.docs.test.ts
pnpm test:integration
pnpm test:coverage

Test types

TypeTypical locationUses real DB?Purpose
Unit / factory testscolocated *.test.ts / *.factory.test.tsNoServices, route factories, UI behavior
Integration testsshared/db/integration-tests/*.test.tsYesDrizzle queries, schema behavior, seed/migration workflows

Tests are colocated with implementation wherever possible.


Service tests

This is the preferred pattern for API services.

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createContentService } from './content.service.js';

describe('createContentService', () => {
  const contentRepository = {
    create: vi.fn(),
    findBySiteId: vi.fn(),
  };

  const eventBus = {
    emit: vi.fn(),
    subscribe: vi.fn(),
    unsubscribeAll: vi.fn(),
  };

  beforeEach(() => vi.clearAllMocks());

  it('emits an event after create', async () => {
    const service = createContentService({
      contentRepository,
      siteRepository: {} as never,
      eventBus,
    });
    contentRepository.create.mockResolvedValue({
      id: 'c-1',
      siteId: 'site-1',
      title: 'Hello',
      body: null,
      slug: null,
      status: 'draft',
      contentType: 'post',
      authorId: null,
      featureImage: null,
      excerpt: null,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    await service.createContent({ title: 'Hello' }, 'site-1', 'user-1');

    expect(eventBus.emit).toHaveBeenCalled();
  });
});

Why this is the default:

  • no vi.mock() for deep module graphs
  • mock shape is explicit in the test file
  • adding dependencies is straightforward

Route factory tests

Route factory tests usually mock middleware helpers plus service dependencies.

The current content route tests are a good reference:

apps/api/src/routes/content/content-factory.test.ts

The pattern is:

  1. mock getAuthContext / requirePermission / requireAuth
  2. pass plain mocked services into the route factory
  3. call routes.request(...)
  4. assert on HTTP status and service calls

Integration tests

Integration tests run against a real PostgreSQL database using the helpers in shared/db/integration-tests/integration-utils.ts.

Current reference:

shared/db/integration-tests/content.integration.test.ts

That file shows the current helpers and flow:

  • setupIntegrationTests() in beforeAll
  • teardownIntegrationTests() in afterAll
  • getTestDb() inside tests
  • cleanTables() between tests

Example shape:

beforeAll(async () => {
  await setupIntegrationTests();
});

afterAll(async () => {
  await teardownIntegrationTests();
});

afterEach(async () => {
  await cleanTables();
});

Use integration tests when you need to verify:

  • real SQL behavior
  • migrations / schema constraints
  • JSON round-tripping
  • multi-tenant isolation at the data layer

Coverage

New code is expected to reach 100% coverage.

pnpm test:coverage

Focus on:

  • success and failure branches
  • event emission paths
  • data shaping helpers
  • authentication / authorization edge cases

Good references in the repo

  • apps/api/src/routes/content/content-factory.test.ts
  • apps/api/src/app.docs.test.ts
  • shared/db/integration-tests/content.integration.test.ts
  • apps/admin/src/components/DashboardLayout.test.tsx

On this page