- Updated skill documentation files to include structured metadata for better organization. - Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese. - Refined sections on usage, best practices, and related skills to ensure consistency across all documentation. - Improved formatting and removed outdated references to streamline the documentation experience. - Added best practices checklists to relevant skills for better usability and adherence to standards.
739 lines
25 KiB
Markdown
739 lines
25 KiB
Markdown
# Các Pattern Testing
|
|
|
|
Comprehensive testing best practices for GoodGo microservices including unit tests, integration tests, E2E tests, Jest configuration, mocking strategies, and debugging techniques.
|
|
> Các thực hành tốt nhất về testing cho microservices GoodGo bao gồm unit tests, integration tests, E2E tests, cấu hình Jest, strategies mocking, và kỹ thuật debugging.
|
|
|
|
## Tổng Quan
|
|
|
|
Testing is a critical component of the GoodGo microservices platform. This guide provides comprehensive patterns and best practices for writing maintainable, reliable tests across all services. It covers test types, Jest configuration, mocking strategies, test utilities, and debugging techniques that ensure code quality and reliability.
|
|
|
|
Testing là thành phần quan trọng của nền tảng microservices GoodGo. Hướng dẫn này cung cấp các patterns và best practices toàn diện để viết các test có thể bảo trì và đáng tin cậy trên tất cả các services. Nó bao gồm các loại test, cấu hình Jest, strategies mocking, test utilities, và kỹ thuật debugging để đảm bảo chất lượng và độ tin cậy của code.
|
|
|
|
## Khi Nào Sử Dụng
|
|
|
|
Use these testing patterns 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
|
|
- Creating test utilities and factories
|
|
|
|
Sử dụng các testing patterns này khi:
|
|
- Viết unit tests cho services, controllers, hoặc repositories
|
|
- Tạo integration tests cho middleware chains
|
|
- Xây dựng E2E tests cho API endpoints
|
|
- Thiết lập cấu hình Jest cho service mới
|
|
- Mocking các dependencies bên ngoài (Prisma, Redis, Auth SDK)
|
|
- Debugging test failures
|
|
- Cải thiện test coverage
|
|
- Tạo test utilities và factories
|
|
|
|
## Khái Niệm Chính
|
|
|
|
### Các Loại Test
|
|
|
|
#### Unit Tests
|
|
|
|
Test individual functions or classes in isolation with all dependencies mocked.
|
|
|
|
Test các functions hoặc classes riêng lẻ một cách cô lập với tất cả dependencies được mock.
|
|
|
|
**Characteristics / Đặc điểm**:
|
|
- **Location / Vị trí**: Next to source files (`*.test.ts`) or in `__tests__/` directory
|
|
- **Scope / Phạm vi**: Single function, method, or class
|
|
- **Dependencies / Dependencies**: All external dependencies mocked
|
|
- **Speed / Tốc độ**: Fast (<1s per test)
|
|
- **Purpose / Mục đích**: Verify logic correctness
|
|
|
|
**Example / Ví dụ**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
|
|
|
#### Integration Tests
|
|
|
|
Test multiple components working together with some real dependencies.
|
|
|
|
Test nhiều components làm việc cùng nhau với một số dependencies thật.
|
|
|
|
**Characteristics / Đặc điểm**:
|
|
- **Location / Vị trí**: `__tests__/` directory (`*.test.ts`)
|
|
- **Scope / Phạm vi**: Multiple components interacting
|
|
- **Dependencies / Dependencies**: Mix of real and mocked dependencies
|
|
- **Speed / Tốc độ**: Medium (1-5s per test)
|
|
- **Purpose / Mục đích**: Verify component integration
|
|
|
|
**Example / Ví dụ**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
|
|
|
#### E2E Tests
|
|
|
|
Test complete request/response cycles through the entire application stack.
|
|
|
|
Test các chu kỳ request/response hoàn chỉnh qua toàn bộ application stack.
|
|
|
|
**Characteristics / Đặc điểm**:
|
|
- **Location / Vị trí**: `__tests__/*.e2e.ts`
|
|
- **Scope / Phạm vi**: Full API workflow from HTTP request to response
|
|
- **Dependencies / Dependencies**: Test database, mocked external services
|
|
- **Speed / Tốc độ**: Slow (5-10s per test)
|
|
- **Purpose / Mục đích**: Verify end-to-end functionality
|
|
|
|
**Example / Ví dụ**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
|
|
|
## Cấu Hình Jest
|
|
|
|
### Cấu Hình Chuẩn
|
|
|
|
The standard Jest configuration for GoodGo services includes TypeScript support, coverage thresholds, and proper test environment setup.
|
|
|
|
**Location / Vị trí**: [`services/iam-service/jest.config.ts`](../../../services/iam-service/jest.config.ts)
|
|
|
|
```typescript
|
|
import type { Config } from 'jest';
|
|
|
|
const config: Config = {
|
|
preset: 'ts-jest',
|
|
testEnvironment: 'node',
|
|
roots: ['<rootDir>/src'],
|
|
testMatch: [
|
|
'**/__tests__/**/*.test.ts',
|
|
'**/__tests__/**/*.spec.ts',
|
|
'**/__tests__/**/*.e2e.ts',
|
|
'**/?(*.)+(spec|test).ts'
|
|
],
|
|
collectCoverageFrom: [
|
|
'src/**/*.ts',
|
|
'!src/**/*.d.ts',
|
|
'!src/main.ts',
|
|
'!src/config/**/*.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,
|
|
resetModules: true
|
|
};
|
|
|
|
export default config;
|
|
```
|
|
|
|
### Điểm Cấu Hình Quan Trọng
|
|
|
|
- **preset: 'ts-jest'**: Enables TypeScript support / Bật hỗ trợ TypeScript
|
|
- **testEnvironment: 'node'**: Sets Node.js environment (not browser) / Thiết lập môi trường Node.js (không phải browser)
|
|
- **coverageThreshold**: Enforces minimum 70% coverage across all metrics / Yêu cầu tối thiểu 70% coverage trên tất cả các metrics
|
|
- **setupFilesAfterEnv**: Loads test setup file before each test suite / Tải file setup test trước mỗi test suite
|
|
- **clearMocks**: Automatically clears mock calls between tests / Tự động xóa mock calls giữa các test
|
|
- **resetModules**: Resets module cache for test isolation / Reset module cache để cô lập test
|
|
|
|
## Files Thiết Lập
|
|
|
|
### File Thiết Lập Test
|
|
|
|
The setup file (`setupTests.ts`) configures global mocks and test utilities that are available across all tests.
|
|
|
|
**Location / Vị trí**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
|
|
|
**Key Features / Tính Năng Chính**:
|
|
- Mock external services (logger, tracing, database, Redis) / Mock các services bên ngoài
|
|
- Configure test environment variables / Cấu hình biến môi trường test
|
|
- Set up global test utilities / Thiết lập test utilities toàn cục
|
|
- Initialize Prometheus mocks / Khởi tạo Prometheus mocks
|
|
|
|
**Example / Ví dụ**:
|
|
```typescript
|
|
import { jest } from '@jest/globals';
|
|
|
|
Mock environment variables for tests
|
|
// VI: Mock biến môi trường cho tests
|
|
process.env.NODE_ENV = 'test';
|
|
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test_db';
|
|
process.env.REDIS_URL = 'redis://localhost:6379/1';
|
|
|
|
Mock external services to avoid real network calls
|
|
// VI: Mock các service bên ngoài để tránh gọi mạng thật
|
|
jest.mock('@goodgo/logger', () => ({
|
|
logger: {
|
|
info: jest.fn(),
|
|
error: jest.fn(),
|
|
warn: jest.fn(),
|
|
debug: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
Global test utilities
|
|
// VI: Utilities test toàn cục
|
|
global.testUtils = {
|
|
createMockReq: (overrides = {}) => ({
|
|
body: {},
|
|
params: {},
|
|
query: {},
|
|
headers: {},
|
|
...overrides,
|
|
}),
|
|
createMockRes: () => {
|
|
const res: any = {};
|
|
res.status = jest.fn().mockReturnValue(res);
|
|
res.json = jest.fn().mockReturnValue(res);
|
|
res.send = jest.fn().mockReturnValue(res);
|
|
return res;
|
|
},
|
|
createMockNext: () => jest.fn(),
|
|
};
|
|
```
|
|
|
|
## Các Pattern Thường Dùng
|
|
|
|
### Pattern Unit Test
|
|
|
|
**Structure / Cấu trúc**: Follow the AAA (Arrange-Act-Assert) pattern for clarity / Tuân theo pattern AAA để rõ ràng.
|
|
|
|
```typescript
|
|
describe('FeatureService', () => {
|
|
let service: FeatureService;
|
|
let mockRepository: any;
|
|
|
|
beforeEach(() => {
|
|
Clear all mocks before each test
|
|
// VI: Xóa tất cả mocks trước mỗi test
|
|
jest.clearAllMocks();
|
|
|
|
mockRepository = {
|
|
findById: jest.fn(),
|
|
create: jest.fn(),
|
|
};
|
|
service = new FeatureService(mockRepository);
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('should create a feature successfully', async () => {
|
|
Arrange
|
|
// VI: Chuẩn bị
|
|
const testData = { name: 'test-feature', title: 'Test Feature' };
|
|
const mockFeature = {
|
|
id: 'test-id',
|
|
name: testData.name,
|
|
// ... other fields
|
|
};
|
|
mockRepository.create.mockResolvedValue(mockFeature);
|
|
|
|
Act
|
|
// VI: Thực hiện
|
|
const result = await service.create(testData);
|
|
|
|
Assert
|
|
// VI: Kiểm tra
|
|
expect(mockRepository.create).toHaveBeenCalledWith(testData);
|
|
expect(result).toEqual(mockFeature);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
|
|
|
### Pattern Integration Test
|
|
|
|
Integration tests verify that multiple components work together correctly / Integration tests xác minh nhiều components làm việc cùng nhau đúng cách.
|
|
|
|
```typescript
|
|
describe('Auth Middleware', () => {
|
|
const mockNext = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should authenticate valid token and attach user to request', () => {
|
|
Arrange
|
|
// VI: Chuẩn bị
|
|
const mockReq = createMockReq({
|
|
headers: { authorization: 'Bearer valid-token' },
|
|
});
|
|
const mockRes = createMockRes();
|
|
|
|
Act
|
|
// VI: Thực hiện
|
|
const middleware = authenticate({ secret: jwtSecret });
|
|
middleware(mockReq as Request, mockRes as Response, mockNext);
|
|
|
|
Assert
|
|
// VI: Kiểm tra
|
|
expect(mockNext).toHaveBeenCalled();
|
|
expect(mockReq.user).toBeDefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
|
|
|
### Pattern E2E Test
|
|
|
|
E2E tests use supertest to test complete HTTP request/response cycles / E2E tests sử dụng supertest để test các chu kỳ HTTP request/response hoàn chỉnh.
|
|
|
|
```typescript
|
|
import request from 'supertest';
|
|
import express from 'express';
|
|
import { createRouter } from '../routes';
|
|
|
|
describe('Feature Endpoints E2E', () => {
|
|
let app: express.Application;
|
|
|
|
beforeAll(() => {
|
|
Set up test environment
|
|
// VI: Thiết lập môi trường test
|
|
process.env.NODE_ENV = 'test';
|
|
app = express();
|
|
app.use(express.json());
|
|
app.use(createRouter());
|
|
});
|
|
|
|
describe('POST /api/v1/features', () => {
|
|
it('should create a feature successfully', async () => {
|
|
Arrange
|
|
// VI: Chuẩn bị
|
|
const featureData = {
|
|
name: 'test-feature',
|
|
title: 'Test Feature',
|
|
description: 'A test feature for E2E testing'
|
|
};
|
|
|
|
Act
|
|
// VI: Thực hiện
|
|
const response = await request(app)
|
|
.post('/api/v1/features')
|
|
.send(featureData)
|
|
.expect(201);
|
|
|
|
Assert
|
|
// VI: Kiểm tra
|
|
expect(response.body).toMatchObject({
|
|
success: true,
|
|
message: 'Feature created successfully / Feature đã được tạo thành công',
|
|
});
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
|
|
|
## Chiến Lược Mocking
|
|
|
|
### Mock Database Prisma
|
|
|
|
Mock Prisma client to avoid real database connections in unit tests / Mock Prisma client để tránh kết nối database thật trong unit tests.
|
|
|
|
```typescript
|
|
// In setupTests.ts or individual test files
|
|
jest.mock('../config/database.config', () => ({
|
|
connectDatabase: jest.fn(),
|
|
prisma: {
|
|
$queryRaw: jest.fn(),
|
|
$disconnect: jest.fn(),
|
|
feature: {
|
|
create: jest.fn(),
|
|
findMany: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
// Usage in tests
|
|
const { prisma } = require('../config/database.config');
|
|
|
|
test('should create user', async () => {
|
|
prisma.feature.create.mockResolvedValue({
|
|
id: 'test-id',
|
|
name: 'test-feature',
|
|
// ... other fields
|
|
});
|
|
|
|
const result = await createFeature({ name: 'test-feature' });
|
|
expect(result).toBeDefined();
|
|
});
|
|
```
|
|
|
|
### Mock Redis
|
|
|
|
Mock Redis client for caching and session management tests / Mock Redis client cho các test caching và quản lý session.
|
|
|
|
```typescript
|
|
jest.mock('../config/redis.config', () => ({
|
|
getRedisClient: jest.fn().mockReturnValue({
|
|
call: jest.fn(),
|
|
connect: jest.fn(),
|
|
disconnect: jest.fn(),
|
|
}),
|
|
}));
|
|
```
|
|
|
|
### Mock External APIs
|
|
|
|
Mock external HTTP calls using Jest mocks / Mock các HTTP calls bên ngoài sử dụng Jest mocks.
|
|
|
|
```typescript
|
|
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' });
|
|
});
|
|
```
|
|
|
|
### Mock Auth SDK
|
|
|
|
Mock authentication SDK functions for middleware and service tests / Mock các functions của authentication SDK cho middleware và service tests.
|
|
|
|
```typescript
|
|
jest.mock('@goodgo/auth-sdk', () => ({
|
|
createToken: jest.fn(),
|
|
verifyToken: jest.fn(),
|
|
extractTokenFromHeader: jest.fn(),
|
|
}));
|
|
|
|
// Setup mock implementations
|
|
(verifyToken as jest.Mock).mockImplementation((token) => {
|
|
if (token === 'valid-token') {
|
|
return { userId: '123', role: 'user' };
|
|
}
|
|
throw new Error('Invalid token');
|
|
});
|
|
```
|
|
|
|
## Utilities Test
|
|
|
|
### Pattern Test Factory
|
|
|
|
Create reusable test data factories for consistent test data / Tạo các factories dữ liệu test có thể tái sử dụng để có dữ liệu test nhất quán.
|
|
|
|
```typescript
|
|
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);
|
|
```
|
|
|
|
### Helpers Mock Request/Response
|
|
|
|
Use global test utilities for creating mock Express request/response objects / Sử dụng test utilities toàn cục để tạo các objects mock Express request/response.
|
|
|
|
```typescript
|
|
// Available via global.testUtils (from setupTests.ts)
|
|
const mockReq = global.testUtils.createMockReq({
|
|
headers: { authorization: 'Bearer token' },
|
|
params: { id: '123' },
|
|
});
|
|
|
|
const mockRes = global.testUtils.createMockRes();
|
|
const mockNext = global.testUtils.createMockNext();
|
|
```
|
|
|
|
## Các Tình Huống Test Thường Gặp
|
|
|
|
### Test Xử Lý Lỗi
|
|
|
|
```typescript
|
|
it('should handle database errors gracefully', async () => {
|
|
Arrange - Mock database error
|
|
// VI: Chuẩn bị - Mock lỗi database
|
|
prismaMock.user.findUnique.mockRejectedValue(
|
|
new Error('Database connection failed')
|
|
);
|
|
|
|
Act & Assert
|
|
// VI: Thực hiện & Kiểm tra
|
|
await expect(userService.findById('123')).rejects.toThrow();
|
|
|
|
// Or for HTTP endpoints
|
|
const response = await request(app)
|
|
.get('/api/users/123')
|
|
.expect(500);
|
|
|
|
expect(response.body).toEqual({
|
|
success: false,
|
|
error: {
|
|
code: 'INTERNAL_ERROR',
|
|
message: 'Internal server error / Lỗi máy chủ nội bộ',
|
|
},
|
|
});
|
|
});
|
|
```
|
|
|
|
### Test Validation
|
|
|
|
```typescript
|
|
describe('Validation', () => {
|
|
it('should reject invalid email', async () => {
|
|
const response = await request(app)
|
|
.post('/api/auth/register')
|
|
.send({ email: 'invalid-email', password: '123456' })
|
|
.expect(400);
|
|
|
|
expect(response.body.error.code).toBe('VALIDATION_ERROR');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Test Authorization
|
|
|
|
```typescript
|
|
it('should deny access for user with incorrect role', () => {
|
|
const mockReq = createMockReq({
|
|
user: { userId: 'user-123', role: 'user' },
|
|
});
|
|
|
|
const middleware = authorize('admin');
|
|
middleware(mockReq as Request, mockRes as Response, mockNext);
|
|
|
|
expect(mockNext).not.toHaveBeenCalled();
|
|
expect(mockStatus).toHaveBeenCalledWith(403);
|
|
});
|
|
```
|
|
|
|
## Lệnh Test
|
|
|
|
Add these scripts to `package.json` / Thêm các scripts này vào `package.json`:
|
|
|
|
```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"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Usage / Sử dụng**:
|
|
- `pnpm test`: Run all tests / Chạy tất cả tests
|
|
- `pnpm test:watch`: Run tests in watch mode / Chạy tests ở chế độ watch
|
|
- `pnpm test:coverage`: Generate coverage report / Tạo báo cáo coverage
|
|
- `pnpm test:unit`: Run only unit tests / Chỉ chạy unit tests
|
|
- `pnpm test:e2e`: Run only E2E tests / Chỉ chạy E2E tests
|
|
- `pnpm test:ci`: Run tests optimized for CI/CD / Chạy tests tối ưu cho CI/CD
|
|
|
|
## Debugging Tests
|
|
|
|
### Cấu Hình Debug VS Code
|
|
|
|
Create `.vscode/launch.json` for debugging tests / Tạo `.vscode/launch.json` để debug tests:
|
|
|
|
```json
|
|
{
|
|
"configurations": [
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Debug Jest Tests",
|
|
"runtimeExecutable": "npm",
|
|
"runtimeArgs": ["test", "--", "--runInBand"],
|
|
"console": "integratedTerminal",
|
|
"internalConsoleOptions": "neverOpen"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Mẹo Debug
|
|
|
|
1. **Use `test.only()`** to run a single test / Sử dụng `test.only()` để chạy một test duy nhất:
|
|
```typescript
|
|
it.only('should test specific behavior', () => {
|
|
// Only this test will run
|
|
});
|
|
```
|
|
|
|
2. **Use `--detectOpenHandles`** for async issues / Sử dụng `--detectOpenHandles` cho các vấn đề async:
|
|
```bash
|
|
pnpm test --detectOpenHandles
|
|
```
|
|
|
|
3. **Use `--runInBand`** for sequential execution / Sử dụng `--runInBand` cho thực thi tuần tự:
|
|
```bash
|
|
pnpm test --runInBand
|
|
```
|
|
|
|
4. **Add temporary console.log** / Thêm console.log tạm thời:
|
|
```typescript
|
|
console.log('Debug value:', someValue);
|
|
```
|
|
|
|
5. **Use debugger breakpoints** in VS Code / Sử dụng breakpoints debugger trong VS Code
|
|
|
|
## Thực Hành Tốt Nhất
|
|
|
|
### Tổ Chức Test
|
|
|
|
- ✅ Each test is independent and isolated / Mỗi test độc lập và cô lập
|
|
- ✅ Tests follow AAA pattern (Arrange-Act-Assert) / Tests tuân theo pattern AAA
|
|
- ✅ Use descriptive test names that explain what is being tested / Sử dụng tên test mô tả giải thích điều đang được test
|
|
- ✅ Group related tests using `describe` blocks / Nhóm các test liên quan sử dụng `describe` blocks
|
|
- ✅ Use `beforeEach`/`afterEach` for setup/cleanup / Sử dụng `beforeEach`/`afterEach` cho setup/cleanup
|
|
|
|
### Mocking
|
|
|
|
- ✅ Mock external dependencies (database, APIs, services) / Mock các dependencies bên ngoài
|
|
- ✅ Use `jest.clearAllMocks()` in `beforeEach` to reset mocks / Sử dụng `jest.clearAllMocks()` trong `beforeEach` để reset mocks
|
|
- ✅ Verify mock calls to ensure correct behavior / Xác minh mock calls để đảm bảo hành vi đúng
|
|
- ✅ Keep mocks simple and focused / Giữ mocks đơn giản và tập trung
|
|
|
|
### Coverage
|
|
|
|
- ✅ Maintain >70% code coverage (as per Jest config) / Duy trì >70% code coverage (theo cấu hình Jest)
|
|
- ✅ Focus on covering critical business logic / Tập trung vào bao phủ logic nghiệp vụ quan trọng
|
|
- ✅ Don't sacrifice test quality for coverage percentage / Không hy sinh chất lượng test cho phần trăm coverage
|
|
- ✅ Review coverage reports regularly / Xem xét báo cáo coverage thường xuyên
|
|
|
|
### Dữ Liệu Test
|
|
|
|
- ✅ Use factories for creating test data / Sử dụng factories để tạo dữ liệu test
|
|
- ✅ Keep test data realistic and representative / Giữ dữ liệu test thực tế và đại diện
|
|
- ✅ Clean up test data after tests (if using real database) / Dọn dẹp dữ liệu test sau tests (nếu dùng database thật)
|
|
- ✅ Use meaningful test values, not just `'test'` or `123` / Sử dụng giá trị test có ý nghĩa, không chỉ `'test'` hoặc `123`
|
|
|
|
### Test Lỗi
|
|
|
|
- ✅ Test both success and error scenarios / Test cả kịch bản thành công và lỗi
|
|
- ✅ Test edge cases and boundary conditions / Test edge cases và điều kiện biên
|
|
- ✅ Test validation errors / Test lỗi validation
|
|
- ✅ Test error messages and error codes / Test thông báo lỗi và mã lỗi
|
|
|
|
### Hiệu Suất
|
|
|
|
- ✅ Keep unit tests fast (<1s each) / Giữ unit tests nhanh (<1s mỗi test)
|
|
- ✅ Avoid unnecessary async operations in unit tests / Tránh các thao tác async không cần thiết trong unit tests
|
|
- ✅ Use `--maxWorkers` in CI for parallel execution / Sử dụng `--maxWorkers` trong CI cho thực thi song song
|
|
- ✅ Don't test implementation details, test behavior / Không test chi tiết implementation, test hành vi
|
|
|
|
## Ví Dụ Từ Dự Án
|
|
|
|
### Ví Dụ Test Thực Tế
|
|
|
|
1. **Unit Test**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
|
2. **Integration Test**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
|
3. **E2E Test**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
|
4. **Setup File**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
|
5. **Jest Config**: [`services/iam-service/jest.config.ts`](../../../services/iam-service/jest.config.ts)
|
|
|
|
### Ví Dụ Cấu Trúc Test
|
|
|
|
- **Repository Tests**: [`services/iam-service/src/modules/feature/__tests__/feature.repository.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.repository.test.ts)
|
|
- **Controller Tests**: [`services/iam-service/src/modules/health/__tests__/health.controller.test.ts`](../../../services/iam-service/src/modules/health/__tests__/health.controller.test.ts)
|
|
- **Middleware Tests**: [`services/iam-service/src/middlewares/__tests__/correlation.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/correlation.middleware.test.ts)
|
|
|
|
## Tham Khảo Nhanh
|
|
|
|
### Cây Quyết Định Loại Test
|
|
|
|
```
|
|
Need to test complete HTTP flow?
|
|
├─ Yes → E2E Test (*.e2e.ts)
|
|
└─ No → Multiple components interacting?
|
|
├─ Yes → Integration Test (*.test.ts)
|
|
└─ No → Unit Test (*.test.ts)
|
|
```
|
|
|
|
### Các Matcher Jest Thường Dùng
|
|
|
|
| Matcher | Purpose / Mục đích | Example / Ví dụ |
|
|
|---------|-------------------|-----------------|
|
|
| `toBe()` | Exact equality / So sánh chính xác | `expect(value).toBe(5)` |
|
|
| `toEqual()` | Deep equality / So sánh sâu | `expect(obj).toEqual({a: 1})` |
|
|
| `toMatchObject()` | Partial match / So sánh một phần | `expect(obj).toMatchObject({a: 1})` |
|
|
| `toHaveBeenCalled()` | Function called / Function đã được gọi | `expect(mockFn).toHaveBeenCalled()` |
|
|
| `toHaveBeenCalledWith()` | Called with args / Gọi với args | `expect(mockFn).toHaveBeenCalledWith('arg')` |
|
|
| `toThrow()` | Throws error / Ném lỗi | `expect(() => fn()).toThrow()` |
|
|
| `toBeDefined()` | Not undefined / Không undefined | `expect(value).toBeDefined()` |
|
|
| `toBeNull()` | Is null / Là null | `expect(value).toBeNull()` |
|
|
| `toContain()` | Array/string contains / Chứa trong array/string | `expect(array).toContain('item')` |
|
|
|
|
### Helpers Mock Function
|
|
|
|
```typescript
|
|
// Create mock
|
|
const mockFn = jest.fn();
|
|
|
|
// Set return value
|
|
mockFn.mockReturnValue('value');
|
|
mockFn.mockResolvedValue('async value');
|
|
mockFn.mockRejectedValue(new Error('error'));
|
|
|
|
// Set implementation
|
|
mockFn.mockImplementation((arg) => arg * 2);
|
|
|
|
// Clear/reset
|
|
mockFn.mockClear(); // Clear call history
|
|
mockFn.mockReset(); // Reset to initial state
|
|
jest.clearAllMocks(); // Clear all mocks
|
|
```
|
|
|
|
## Skills Liên Quan
|
|
|
|
- **[Comment Code](./comment-code.md)**: Writing bilingual comments in tests / Viết comments song ngữ trong tests
|
|
- **[Security](./security.md)**: Testing security-critical code / Test code bảo mật quan trọng
|
|
- **[API Design](./api-design.md)**: Testing API endpoints and responses / Test API endpoints và responses
|
|
- **[Project Rules](./project-rules.md)**: Code organization for tests / Tổ chức code cho tests
|
|
|
|
## Tài Nguyên
|
|
|
|
### Tài Liệu Chính Thức
|
|
|
|
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
|
- [Supertest Documentation](https://github.com/visionmedia/supertest)
|
|
- [jest-mock-extended](https://github.com/marchaos/jest-mock-extended)
|
|
|
|
### Tài Liệu Nội Bộ
|
|
|
|
- [Service Development Guide](../guides/development.md)
|
|
- [Local Development Setup](../guides/local-development.md)
|
|
- [Troubleshooting Guide](../guides/troubleshooting.md)
|
|
|
|
### Công Cụ
|
|
|
|
- **Jest**: JavaScript testing framework / Framework testing JavaScript
|
|
- **Supertest**: HTTP assertion library / Thư viện assertion HTTP
|
|
- **jest-mock-extended**: Enhanced mocking for TypeScript / Mock nâng cao cho TypeScript
|
|
- **ioredis-mock**: Redis mock for testing / Mock Redis cho testing
|