Tutorial: Writing Unit Tests
This guide shows how to write and run unit tests for Open Mercato using Jest + ts-jest. It covers services, API handlers, and utilities, and explains how to keep tests out of the runtime bundle.
Prerequisites
- Node.js 20+
- Jest + ts-jest already configured in this repo
- Aliases resolved in tests via
jest.config.cjs
Running Tests
- All tests:
yarn test - Watch mode:
yarn test:watch
Jest Aliases (in tests)
@/generated/*→generated/*@/lib/*→packages/shared/src/lib/*@/types/*→packages/shared/src/types/*@open-mercato/core/*,@open-mercato/example/*,@open-mercato/cli/*,@open-mercato/shared/*- Fallback:
@/*→src/*
See jest.config.cjs: moduleNameMapper for the full mapping.
Patterns
1) Services (no DB)
Keep service tests fast by stubbing the EM (or other gateways) with minimal methods.
// packages/core/src/modules/auth/services/__tests__/authService.test.ts
import { AuthService } from '@open-mercato/core/modules/auth/services/authService'
describe('AuthService', () => {
it('hashes password on create', async () => {
const em: any = { calls: [], persistAndFlush: async (e: any) => em.calls.push(['persistAndFlush', e]) }
const svc = new AuthService(em)
await svc.createUser({ email: 'a@b.c', password: 'secret' } as any)
expect(em.calls[0][0]).toBe('persistAndFlush')
})
})
Tips:
- Stub only what you use; avoid spinning up real DB connections.
- Extract common stubs to
__tests__/helpersif reused.
2) API Handlers (mock DI)
Import handlers and call them directly. Mock the DI container so handlers get fake services.
// packages/core/src/modules/auth/api/__tests__/login.test.ts
import { POST } from '@open-mercato/core/modules/auth/api/login'
jest.mock('@/lib/di/container', () => ({
createRequestContainer: async () => ({
resolve: () => ({
findUserByEmail: async () => ({ id: 1, email: 'admin@acme.com', passwordHash: 'hash', tenant: { id: 1 }, organization: { id: 1 } }),
verifyPassword: async () => true,
issueSession: async () => ({ token: 'jwt-token' }),
}),
}),
}))
function makeRequest(body: any) {
return new Request('http://localhost/api/auth/login', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
})
}
describe('auth login', () => {
it('returns 200 for valid credentials', async () => {
const res = await POST(makeRequest({ email: 'admin@acme.com', password: 'secret' }))
expect(res.status).toBe(200)
})
})
Tips:
- Use
Requestwith JSON bodies to simulate HTTP. - Assert on status codes and minimal response body (not framework internals).
3) Utilities
Plain utilities are simple to test:
import { someUtil } from '@open-mercato/shared/lib/utils'
test('someUtil', () => {
expect(someUtil('x')).toBe('X')
})
File Layout and Discovery
- Co-locate tests in
__tests__folders next to code. - Filenames:
*.test.tsor*.test.tsx - Jest
testMatch:**/__tests__/**/*.test.(ts|tsx)
Keeping Tests Out of Runtime
Our module generator explicitly ignores tests so dev/runtime doesn’t import them:
- Skips folders named
__tests__and__mocks__ - Skips files ending with
.test.ts,.spec.ts
If you introduce new patterns, ensure they don’t live under module api/ unless named with .test.ts.
Troubleshooting
- “jest is not defined” during
yarn dev: means a test leaked into the runtime. Ensure tests are under__tests__and the generator filters apply. - Module not found in tests: verify alias in
jest.config.cjsand the file path.
You’re set — write focused tests, mock I/O, and keep the loop fast.