Files
pos-system/.agent/rules/testing-patterns.md

180 lines
5.0 KiB
Markdown

---
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: ['<rootDir>/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<PrismaClient>();
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