Testing Guide
Made Open has 482 test files (*.test.ts + *.test.tsx under apps/hub, apps/web, and packages/*) across 11 pnpm workspace packages, using Vitest for all TypeScript projects. (Individual test-case counts are not independently tracked in this guide.)
Running Tests
# All tests across the monorepo
pnpm test
# Individual packages
pnpm -F @made-open/hub test
pnpm -F @made-open/web test
pnpm -F @made-open/shared test
pnpm -F @made-open/ai test
pnpm -F @made-open/sdk test
pnpm -F @made-open/plugin-sdk test
# With coverage (hub)
pnpm -F @made-open/hub test:coverage
# Watch mode (re-runs on file change)
pnpm -F @made-open/hub test:watch
Test Architecture
Framework
All TypeScript packages use Vitest with the node environment and globals: true. Configuration is in vitest.config.ts at each package root.
Coverage Thresholds
The hub enforces strict coverage thresholds:
| Metric | Threshold |
|---|---|
| Lines | 99% |
| Functions | 100% |
| Branches | 98% |
| Statements | 99% |
Coverage provider: V8 (fast) with reporters: text, json, html, lcov. Defined in apps/hub/vitest.config.ts.
Unit Tests
Unit tests live alongside source files as *.test.ts. Common patterns:
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock external dependencies
vi.mock('../services/data/DataService.js', () => ({
DataService: vi.fn().mockImplementation(() => ({
query: vi.fn().mockResolvedValue([]),
getById: vi.fn().mockResolvedValue(null),
})),
}));
describe('MyService', () => {
let service: MyService;
beforeEach(() => {
service = new MyService(/* mocked deps */);
});
it('should do the thing', async () => {
const result = await service.doThing();
expect(result).toBeDefined();
});
});
Integration Tests
Integration tests use the TestHarness pattern in apps/hub/src/__tests__/integration/:
| Test | What It Exercises |
|---|---|
health.integration.test.ts | API health endpoint |
contacts.integration.test.ts | Contact CRUD end-to-end |
demo-flow.integration.test.ts | Full MVP demo flow |
agent.integration.test.ts | AI agent queries |
platform.integration.test.ts | Platform-wide integration |
graphql.integration.test.ts | GraphQL API |
The TestHarness (apps/hub/src/__tests__/integration/TestHarness.ts) bootstraps a minimal hub instance with real service instances but mocked external connections (NATS, Meilisearch, Supabase).
Real-world integration tests (requiring Docker) live in __tests__/integration/realworld/ and are excluded from the default test run. Run them with:
pnpm -F @made-open/hub test:integration
Route Tests
Every route file has a corresponding test:
apps/hub/src/api/routes/
├── credentials.ts # Route implementation
├── credentials.test.ts # Route test
├── marketplace.ts
├── marketplace.test.ts
└── ... (77 route/test pairs out of 84 route files; `capture.ts` and `whatsappWebhook.ts` currently have no matching `.test.ts`)
Plugin Tests
Plugin tests validate sandbox behaviour:
apps/hub/plugins/channels/twilio/
├── index.ts # Plugin implementation
├── plugin.json # Manifest
└── twilio-channel.test.ts # Tests
Mock Patterns
Common mocking approaches:
vi.mock()— Module-level mocks for service dependenciesvi.fn()— Individual function mocks with return value controlvi.spyOn()— Spy on existing methods without replacing- Inline mock factories —
vi.fn().mockImplementation(() => ({...}))for complex service interfaces
Test Organization by Package
| Package | Focus |
|---|---|
@made-open/hub | Services, routes, plugins, integration |
@made-open/web | Components, pages, hooks |
@made-open/shared | Types, schemas, validation |
@made-open/ai | RAG, routing, embeddings |
@made-open/sdk | Client methods, auth |
@made-open/plugin-sdk | SDK helpers, types |
Per-package test-case counts are not tracked here. The file-level total across the monorepo is 482 (*.test.ts + *.test.tsx).
CI Expectations
All tests must pass before merging. The test suite runs in ~60 seconds on a modern machine. Coverage reports are generated in coverage/ directories at each package root.