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:coverageTest types
| Type | Typical location | Uses real DB? | Purpose |
|---|---|---|---|
| Unit / factory tests | colocated *.test.ts / *.factory.test.ts | No | Services, route factories, UI behavior |
| Integration tests | shared/db/integration-tests/*.test.ts | Yes | Drizzle 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.tsThe pattern is:
- mock
getAuthContext/requirePermission/requireAuth - pass plain mocked services into the route factory
- call
routes.request(...) - 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.tsThat file shows the current helpers and flow:
setupIntegrationTests()inbeforeAllteardownIntegrationTests()inafterAllgetTestDb()inside testscleanTables()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:coverageFocus 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.tsapps/api/src/app.docs.test.tsshared/db/integration-tests/content.integration.test.tsapps/admin/src/components/DashboardLayout.test.tsx