⚠️ MANDATORY: All new implementations MUST include comprehensive unit tests.
Pricey uses Vitest as its primary testing framework across all packages. This document provides a quick reference for running and writing tests.
For comprehensive testing guidelines, see Testing Mandate.
# Run all tests across all packages
pnpm test
# Run tests in watch mode (auto-rerun on changes)
pnpm test:watch
# Run tests with coverage report
pnpm test:coverage
# Run tests with visual UI
pnpm test:ui
# Run tests for a specific package
pnpm --filter @pricey/api-gateway test
# Run a specific test file
pnpm --filter @pricey/api-gateway test src/utils/file-validation.test.ts-
Create a test file next to your source file:
src/utils/ ├── date-parser.ts └── date-parser.test.ts # ← Co-located test file -
Write your tests:
import { describe, it, expect } from 'vitest'; import { parseDate } from './date-parser'; describe('parseDate', () => { it('should parse ISO date format', () => { expect(parseDate('2024-01-15')).toEqual(new Date('2024-01-15')); }); it('should return null for invalid dates', () => { expect(parseDate('not-a-date')).toBeNull(); }); });
-
Run your tests:
pnpm test
| Package/App | Minimum Coverage |
|---|---|
packages/* |
80% |
apps/api-gateway |
75% |
apps/ocr-service |
75% |
apps/web |
70% |
✅ ALWAYS test:
- Pure functions
- Validation schemas (Zod)
- API routes
- Business logic
- Error handling
- Edge cases
❌ Don't test:
- Third-party libraries
- Simple getters/setters
- Type definitions
// src/utils/format.ts
export function formatPrice(amount: number): string {
return `$${amount.toFixed(2)}`;
}
// src/utils/format.test.ts
import { describe, it, expect } from 'vitest';
import { formatPrice } from './format';
describe('formatPrice', () => {
it('should format positive amounts', () => {
expect(formatPrice(10.5)).toBe('$10.50');
});
it('should format zero', () => {
expect(formatPrice(0)).toBe('$0.00');
});
it('should handle negative amounts', () => {
expect(formatPrice(-5.99)).toBe('$-5.99');
});
it('should round to two decimal places', () => {
expect(formatPrice(10.999)).toBe('$11.00');
});
});// src/services/email.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { EmailService } from './email';
describe('EmailService', () => {
let emailService: EmailService;
let mockSendEmail: any;
beforeEach(() => {
mockSendEmail = vi.fn().mockResolvedValue({ success: true });
emailService = new EmailService(mockSendEmail);
});
it('should send email with correct parameters', async () => {
await emailService.sendWelcomeEmail('[email protected]');
expect(mockSendEmail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Welcome to Pricey',
template: 'welcome',
});
});
it('should handle email errors', async () => {
mockSendEmail.mockRejectedValueOnce(new Error('SMTP Error'));
await expect(
emailService.sendWelcomeEmail('[email protected]')
).rejects.toThrow('SMTP Error');
});
});// src/schemas/user.test.ts
import { describe, it, expect } from 'vitest';
import { userSchema } from './user';
describe('userSchema', () => {
it('should validate correct user data', () => {
const validUser = {
email: '[email protected]',
name: 'John Doe',
age: 30,
};
const result = userSchema.safeParse(validUser);
expect(result.success).toBe(true);
});
it('should reject invalid email', () => {
const invalidUser = {
email: 'not-an-email',
name: 'John Doe',
};
const result = userSchema.safeParse(invalidUser);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.errors[0].path).toEqual(['email']);
}
});
});// src/routes/health.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { build } from '../app';
import type { FastifyInstance } from 'fastify';
describe('Health Routes', () => {
let app: FastifyInstance;
beforeEach(async () => {
app = await build({ logger: false });
await app.ready();
});
afterEach(async () => {
await app.close();
});
it('should return 200 OK', async () => {
const response = await app.inject({
method: 'GET',
url: '/health',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toMatchObject({
status: 'ok',
});
});
});Tests run automatically on:
- ✅ Every pull request
- ✅ Push to
mainordevelopbranches - ✅ Before deployment
Tests run automatically before commits:
# Install git hooks
pnpm prepare
# Now tests will run on changed files before each commit# Install dependencies
pnpm install- Check environment variables
- Verify database is seeded correctly
- Check for timezone issues
# Check which files are not covered
pnpm test:coverage
# Open the HTML report
open coverage/index.html- Testing Mandate - Comprehensive testing guide
- Vitest Documentation - Official Vitest docs
- Testing Strategy - Detailed testing strategy
- api-gateway test examples
- OCR parser tests (to be added)
- Check existing tests for patterns
- Ask in team chat
- Review the testing mandate document
Remember: Write tests. Not too many. Mostly integration. 🎯