--- trigger: always_on --- # Testing Patterns for GoodGo Microservices ## When to Use This Skill Use this skill when: - Writing unit tests for services, controllers, or repositories - Creating integration tests for middleware chains - Building E2E tests for API endpoints - Setting up Jest configuration for a new service - Mocking external dependencies (Prisma, Redis, Auth SDK) - Debugging test failures - Improving test coverage ## Core Concepts ### Test Types | Type | Location | Speed | Dependencies | |------|----------|-------|--------------| | **Unit** | `*.test.ts` (next to source) | <1s | All mocked | | **Integration** | `__tests__/` | 1-5s | Partial mocking | | **E2E** | `__tests__/*.e2e.ts` | 5-10s | Test DB, mocked external | ## Key Patterns ### Jest Configuration ```typescript // jest.config.ts export default { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['**/__tests__/**/*.test.ts', '**/__tests__/**/*.e2e.ts'], coverageThreshold: { global: { branches: 70, functions: 70, lines: 70 } }, setupFilesAfterEnv: ['/src/__tests__/setupTests.ts'], }; ``` ### Unit Test Pattern ```typescript describe('FeatureService', () => { let service: FeatureService; let mockRepository: any; beforeEach(() => { mockRepository = { findById: jest.fn(), create: jest.fn() }; service = new FeatureService(mockRepository); }); it('should return feature when found', async () => { mockRepository.findById.mockResolvedValue({ id: '1', name: 'Test' }); const result = await service.findById('1'); expect(result).toEqual({ id: '1', name: 'Test' }); }); }); ``` ### E2E Test Pattern ```typescript describe('POST /api/features', () => { it('should create a new feature', async () => { const response = await request .post('/api/features') .set('Authorization', 'Bearer valid-token') .send({ name: 'New Feature' }) .expect(201); expect(response.body).toMatchObject({ success: true, data: { name: 'New Feature' } }); }); }); ``` ### Mock Prisma ```typescript import { mockDeep } from 'jest-mock-extended'; import { PrismaClient } from '@prisma/client'; export const prismaMock = mockDeep(); jest.mock('../prisma', () => ({ default: prismaMock })); // Usage prismaMock.user.create.mockResolvedValue({ id: '1', email: 'test@example.com' }); ``` ### Test Factory ```typescript export class TestFactory { static createUser(overrides = {}) { return { id: 'test-user-1', email: 'test@example.com', ...overrides }; } static createAuthToken(userId: string) { return jwt.sign({ userId }, 'test-secret'); } } ``` ## Best Practices - Each test is independent and isolated - Tests follow AAA pattern (Arrange-Act-Assert) - Mock external dependencies - Test edge cases and error scenarios - Keep tests simple and focused - Use descriptive test names - Maintain >70% code coverage ## Common Mistakes 1. **Testing Implementation Details**: Tests break on refactoring ```typescript // BAD: expect(service['privateMethod']()).toBe(true); // GOOD: expect(await service.processOrder(order)).toEqual({ success: true }); ``` 2. **Shared Mutable State**: Tests affect each other ```typescript // BAD: let counter = 0; (shared across tests) // GOOD: beforeEach(() => { counter = 0; }); ``` 3. **Not Mocking External Services**: Tests are slow and flaky ```typescript // BAD: await fetch('https://api.example.com/data'); // GOOD: jest.spyOn(httpClient, 'get').mockResolvedValue({ data: mockData }); ``` 4. **Missing Edge Cases**: Only testing happy path ```typescript // GOOD: Include error cases test('creates user', async () => { ... }); test('throws on duplicate email', async () => { ... }); test('validates email format', async () => { ... }); ``` ## Quick Reference **Coverage Targets:** - Global: 70%+ (branches, functions, lines) - Critical paths: 90%+ - Repositories/Services: 80%+ **Essential Commands:** ```bash pnpm test # Run all tests pnpm test:watch # Watch mode pnpm test:coverage # With coverage pnpm test -- --runInBand # Sequential (for debugging) pnpm test -- UserService # Run specific test file ``` **Mock Imports:** ```typescript import { mockDeep } from 'jest-mock-extended'; import { prismaMock } from '../__mocks__/prisma'; import supertest from 'supertest'; ``` **Debug Tips:** 1. Use `test.only()` to run single test 2. Use `--detectOpenHandles` for async issues 3. Use `--runInBand` for sequential execution 4. Add `console.log()` statements temporarily ## Resources - [Jest Documentation](https://jestjs.io/docs/getting-started) - Testing framework - [Supertest](https://github.com/ladjs/supertest) - HTTP assertions - [jest-mock-extended](https://github.com/marchaos/jest-mock-extended) - TypeScript mocks - [Detailed Code Examples](./references/REFERENCE.md) - [Database Prisma](../database-prisma/SKILL.md) - Database mocking patterns - [Error Handling](../error-handling-patterns/SKILL.md) - Error testing