180 lines
5.0 KiB
Markdown
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
|