Files
pos-system/.cursor/skills/testing-patterns/references/REFERENCE.md
Ho Ngoc Hai ed1eb2b6e4 Update skill metadata and enhance documentation across multiple skills
- Change 'dependencies' to 'compatibility' in various skills for consistency
- Add detailed examples and best practices to improve clarity in api-design, api-gateway-advanced, data-consistency-patterns, database-prisma, deployment-kubernetes, event-driven-architecture, inter-service-communication, observability-monitoring, security, and testing-patterns
- Refine Common Mistakes sections with BAD/GOOD code examples for better learning

All skills now feature improved structure and comprehensive guidance for developers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-01-01 16:14:11 +07:00

9.1 KiB

Testing Patterns - Detailed Reference

This reference contains detailed code examples for testing patterns in GoodGo microservices.

Jest Configuration

// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: [
    '**/__tests__/**/*.test.ts',
    '**/__tests__/**/*.e2e.ts',
    '**/?(*.)+(spec|test).ts'
  ],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/main.ts',
    '!src/config/**/*.ts'
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  coverageThreshold: {
    global: {
      branches: 70,
      functions: 70,
      lines: 70,
      statements: 70
    }
  },
  setupFilesAfterEnv: ['<rootDir>/src/__tests__/setupTests.ts'],
  testTimeout: 10000,
  clearMocks: true
};

export default config;

Setup Files

// src/__tests__/setupTests.ts
import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaClient } from '@prisma/client';

// Mock Prisma
jest.mock('../prisma', () => ({
  __esModule: true,
  default: mockDeep<PrismaClient>()
}));

// Mock Redis
jest.mock('ioredis', () => {
  const Redis = jest.requireActual('ioredis-mock');
  return Redis;
});

// Global test utilities
global.testUtils = {
  generateId: () => `test-${Date.now()}`,
  createMockRequest: () => ({
    headers: {},
    body: {},
    query: {},
    params: {}
  }),
  createMockResponse: () => {
    const res: any = {};
    res.status = jest.fn().mockReturnValue(res);
    res.json = jest.fn().mockReturnValue(res);
    res.send = jest.fn().mockReturnValue(res);
    return res;
  }
};

beforeEach(() => {
  jest.clearAllMocks();
});

Unit Test Pattern

// feature.service.test.ts
import { FeatureService } from './feature.service';
import { mockDeep } from 'jest-mock-extended';

describe('FeatureService', () => {
  let service: FeatureService;
  let mockRepository: any;

  beforeEach(() => {
    mockRepository = {
      findById: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn()
    };
    service = new FeatureService(mockRepository);
  });

  describe('findById', () => {
    it('should return feature when found', async () => {
      // Arrange
      const mockFeature = { id: '1', name: 'Test Feature' };
      mockRepository.findById.mockResolvedValue(mockFeature);

      // Act
      const result = await service.findById('1');

      // Assert
      expect(result).toEqual(mockFeature);
      expect(mockRepository.findById).toHaveBeenCalledWith('1');
    });

    it('should throw error when feature not found', async () => {
      // Arrange
      mockRepository.findById.mockResolvedValue(null);

      // Act & Assert
      await expect(service.findById('999')).rejects.toThrow('Feature not found');
    });
  });
});

Integration Test Pattern

// auth.middleware.test.ts
import { authMiddleware } from '../auth.middleware';
import { createMockRequest, createMockResponse } from '../../test-utils';
import jwt from 'jsonwebtoken';

describe('Auth Middleware', () => {
  const mockNext = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should call next when valid token provided', async () => {
    // Arrange
    const req = createMockRequest();
    const res = createMockResponse();
    const token = jwt.sign({ userId: '123' }, 'secret');
    req.headers.authorization = `Bearer ${token}`;

    // Act
    await authMiddleware(req, res, mockNext);

    // Assert
    expect(mockNext).toHaveBeenCalled();
    expect(req.user).toEqual({ userId: '123' });
  });

  it('should return 401 when no token provided', async () => {
    // Arrange
    const req = createMockRequest();
    const res = createMockResponse();

    // Act
    await authMiddleware(req, res, mockNext);

    // Assert
    expect(res.status).toHaveBeenCalledWith(401);
    expect(mockNext).not.toHaveBeenCalled();
  });
});

E2E Test Pattern

// feature.e2e.ts
import supertest from 'supertest';
import { createApp } from '../app';
import { prisma } from '../prisma';

describe('Feature API E2E', () => {
  let app: any;
  let request: any;

  beforeAll(async () => {
    app = await createApp();
    request = supertest(app);
  });

  afterAll(async () => {
    await prisma.$disconnect();
  });

  beforeEach(async () => {
    await prisma.feature.deleteMany();
  });

  describe('POST /api/features', () => {
    it('should create a new feature', async () => {
      // Arrange
      const featureData = {
        name: 'New Feature',
        description: 'Feature description'
      };

      // Act
      const response = await request
        .post('/api/features')
        .set('Authorization', 'Bearer valid-token')
        .send(featureData)
        .expect(201);

      // Assert
      expect(response.body).toMatchObject({
        success: true,
        data: {
          name: 'New Feature',
          description: 'Feature description'
        }
      });

      const created = await prisma.feature.findFirst({
        where: { name: 'New Feature' }
      });
      expect(created).toBeDefined();
    });
  });
});

Mocking Strategies

Mock Prisma

// __mocks__/prisma.ts
import { PrismaClient } from '@prisma/client';
import { mockDeep, DeepMockProxy } from 'jest-mock-extended';

export const prismaMock = mockDeep<PrismaClient>();

jest.mock('../src/prisma', () => ({
  __esModule: true,
  default: prismaMock,
}));

// Usage in tests
import { prismaMock } from '../__mocks__/prisma';

test('should create user', async () => {
  const user = { id: '1', email: 'test@example.com' };
  prismaMock.user.create.mockResolvedValue(user);

  const result = await createUser({ email: 'test@example.com' });
  expect(result).toEqual(user);
});

Mock Redis

// __mocks__/redis.ts
import Redis from 'ioredis-mock';

export const redisMock = new Redis();

// Usage in tests
test('should cache value', async () => {
  const cache = new CacheService(redisMock);
  await cache.set('key', 'value');

  const result = await cache.get('key');
  expect(result).toBe('value');
});

Mock External APIs

// Mock axios
jest.mock('axios');
import axios from 'axios';
const mockedAxios = axios as jest.Mocked<typeof axios>;

test('should fetch external data', async () => {
  mockedAxios.get.mockResolvedValue({
    data: { result: 'success' }
  });

  const result = await fetchExternalData();
  expect(result).toEqual({ result: 'success' });
});

Testing Utilities

// test-utils.ts
export class TestFactory {
  static createUser(overrides = {}) {
    return {
      id: 'test-user-1',
      email: 'test@example.com',
      name: 'Test User',
      createdAt: new Date(),
      ...overrides
    };
  }

  static createAuthToken(userId: string) {
    return jwt.sign({ userId }, 'test-secret');
  }

  static async cleanDatabase() {
    await prisma.user.deleteMany();
    await prisma.feature.deleteMany();
  }
}

// Usage
const user = TestFactory.createUser({ name: 'Custom Name' });
const token = TestFactory.createAuthToken(user.id);

Common Test Scenarios

Testing Error Handling

test('should handle database errors gracefully', async () => {
  prismaMock.user.findUnique.mockRejectedValue(
    new Error('Database connection failed')
  );

  const response = await request
    .get('/api/users/123')
    .expect(500);

  expect(response.body).toEqual({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: 'Internal server error'
    }
  });
});

Testing Validation

describe('Validation', () => {
  it('should reject invalid email', async () => {
    const response = await request
      .post('/api/auth/register')
      .send({ email: 'invalid-email', password: '123456' })
      .expect(400);

    expect(response.body.error.code).toBe('VALIDATION_ERROR');
  });
});

Testing Pagination

test('should paginate results', async () => {
  // Create test data
  const items = Array(25).fill(null).map((_, i) => ({
    id: `item-${i}`,
    name: `Item ${i}`
  }));

  prismaMock.item.findMany.mockResolvedValue(items.slice(0, 10));
  prismaMock.item.count.mockResolvedValue(25);

  const response = await request
    .get('/api/items?page=1&limit=10')
    .expect(200);

  expect(response.body).toEqual({
    success: true,
    data: items.slice(0, 10),
    pagination: {
      page: 1,
      limit: 10,
      total: 25,
      totalPages: 3
    }
  });
});

VS Code Debug Configuration

// .vscode/launch.json
{
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["test", "--", "--runInBand"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

Test Commands

// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:unit": "jest --testPathPattern=\\.test\\.ts$",
    "test:e2e": "jest --testPathPattern=\\.e2e\\.ts$",
    "test:ci": "jest --coverage --silent --maxWorkers=2"
  }
}