---
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
