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

5.0 KiB

trigger
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

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

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

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

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

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

    // BAD: expect(service['privateMethod']()).toBe(true);
    // GOOD: expect(await service.processOrder(order)).toEqual({ success: true });
    
  2. Shared Mutable State: Tests affect each other

    // BAD: let counter = 0; (shared across tests)
    // GOOD: beforeEach(() => { counter = 0; });
    
  3. Not Mocking External Services: Tests are slow and flaky

    // BAD: await fetch('https://api.example.com/data');
    // GOOD: jest.spyOn(httpClient, 'get').mockResolvedValue({ data: mockData });
    
  4. Missing Edge Cases: Only testing happy path

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

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:

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