typescript-unit-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnit Testing Skill
单元测试技能
Unit testing validates individual functions, methods, and classes in isolation by mocking all external dependencies.
单元测试通过模拟所有外部依赖,独立验证单个函数、方法和类的功能。
Workflows
工作流
For guided, step-by-step execution of unit testing tasks, use the appropriate workflow:
| Workflow | Purpose | When to Use |
|---|---|---|
| Setup | Initialize test infrastructure | New project or missing test setup |
| Writing | Write new unit tests | Creating tests for components |
| Reviewing | Review existing tests | Code review, quality audit |
| Running | Execute tests | Running tests, analyzing results |
| Debugging | Fix failing tests | Tests failing, need diagnosis |
| Optimizing | Improve test performance | Slow tests, maintainability |
如需按步骤执行单元测试任务,请使用对应的工作流:
| 工作流 | 用途 | 适用场景 |
|---|---|---|
| 配置 | 初始化测试基础设施 | 新项目或缺少测试配置的项目 |
| 编写 | 编写新单元测试 | 为组件创建测试 |
| 评审 | 评审现有测试 | 代码评审、质量审计 |
| 运行 | 执行测试 | 运行测试、分析结果 |
| 调试 | 修复失败测试 | 测试失败、需要诊断问题 |
| 优化 | 提升测试性能 | 测试缓慢、需要提升可维护性 |
Workflow Selection Guide
工作流选择指南
IMPORTANT: Before starting any testing task, identify the user's intent and load the appropriate workflow.
重要提示:在开始任何测试任务前,请先明确用户意图,再加载对应的工作流。
Detect User Intent → Select Workflow
识别用户意图 → 选择工作流
| User Says / Wants | Workflow to Load | File |
|---|---|---|
| "Set up tests", "configure Jest", "add testing to project", "install test dependencies" | Setup | |
| "Write tests", "add tests", "create tests", "test this service/controller" | Writing | |
| "Review tests", "check test quality", "audit tests", "are these tests good?" | Reviewing | |
| "Run tests", "execute tests", "check if tests pass", "show test results" | Running | |
| "Fix tests", "debug tests", "tests are failing", "why is this test broken?" | Debugging | |
| "Speed up tests", "optimize tests", "tests are slow", "fix open handles" | Optimizing | |
| 用户需求 | 应加载的工作流 | 文件 |
|---|---|---|
| "配置测试"、"配置Jest"、"为项目添加测试"、"安装测试依赖" | 配置 | |
| "编写测试"、"添加测试"、"创建测试"、"测试这个服务/控制器" | 编写 | |
| "评审测试"、"检查测试质量"、"审计测试"、"这些测试合格吗?" | 评审 | |
| "运行测试"、"执行测试"、"检查测试是否通过"、"展示测试结果" | 运行 | |
| "修复测试"、"调试测试"、"测试失败了"、"这个测试为什么坏了?" | 调试 | |
| "加速测试"、"优化测试"、"测试太慢了"、"修复未关闭句柄" | 优化 | |
Workflow Execution Protocol
工作流执行规范
- ALWAYS load the workflow file first - Read the full workflow before taking action
- Follow each step in order - Complete checkpoints before proceeding
- Load knowledge files as directed - Each workflow specifies which files to read
references/ - Verify compliance after completion - Re-read relevant reference files to ensure quality
- 必须先加载工作流文件 - 在采取行动前,先完整阅读工作流内容
- 按顺序执行每一步 - 完成检查点后再继续下一步
- 按要求加载知识库文件 - 每个工作流会指定需要读取的下的文件
references/ - 完成后验证合规性 - 重新阅读相关参考文件,确保质量达标
Knowledge Base Structure
知识库结构
references/
├── common/ # Core testing fundamentals
│ ├── knowledge.md # Testing philosophy and test pyramid
│ ├── rules.md # Mandatory testing rules (AAA, naming, coverage)
│ ├── assertions.md # Assertion patterns and matchers
│ ├── examples.md # Comprehensive examples by category
│ ├── detect-open-handles.md # Open handle detection and cleanup
│ └── performance-optimization.md # Jest runtime optimization
│
├── nestjs/ # NestJS component testing
│ ├── services.md # Service/usecase testing patterns
│ ├── controllers.md # Controller testing patterns
│ ├── guards.md # Guard testing patterns
│ ├── interceptors.md # Interceptor testing patterns
│ └── pipes-filters.md # Pipe and filter testing
│
├── mocking/ # Mock patterns and strategies
│ ├── deep-mocked.md # @golevelup/ts-jest patterns
│ ├── jest-native.md # Jest.fn, spyOn, mock patterns
│ └── factories.md # Test data factory patterns
│
├── repository/ # Repository testing
│ ├── mongodb.md # mongodb-memory-server patterns
│ └── postgres.md # pg-mem patterns
│
├── kafka/ # NestJS Kafka microservices testing
│ └── kafka.md # ClientKafka, @MessagePattern, @EventPattern handlers
│
└── redis/ # Redis cache testing
└── redis.md # Cache operations, health checks, graceful degradationreferences/
├── common/ # 核心测试基础
│ ├── knowledge.md # 测试理念与测试金字塔
│ ├── rules.md # 强制测试规则(AAA、命名、覆盖率)
│ ├── assertions.md # 断言模式与匹配器
│ ├── examples.md # 按类别划分的综合示例
│ ├── detect-open-handles.md # 未关闭句柄的检测与清理
│ └── performance-optimization.md # Jest运行时优化
│
├── nestjs/ # NestJS组件测试
│ ├── services.md # 服务/用例测试模式
│ ├── controllers.md # 控制器测试模式
│ ├── guards.md # 守卫测试模式
│ ├── interceptors.md # 拦截器测试模式
│ └── pipes-filters.md # 管道与过滤器测试
│
├── mocking/ # 模拟模式与策略
│ ├── deep-mocked.md # @golevelup/ts-jest模式
│ ├── jest-native.md # Jest.fn、spyOn、模拟模式
│ └── factories.md # 测试数据工厂模式
│
├── repository/ # 仓库测试
│ ├── mongodb.md # mongodb-memory-server模式
│ └── postgres.md # pg-mem模式
│
├── kafka/ # NestJS Kafka微服务测试
│ └── kafka.md # ClientKafka、@MessagePattern、@EventPattern处理器
│
└── redis/ # Redis缓存测试
└── redis.md # 缓存操作、健康检查、优雅降级Quick Reference by Task
按任务分类的快速参考
Write Unit Tests
编写单元测试
- MANDATORY: Read - AAA pattern, naming, coverage
references/common/rules.md - Read - Assertion best practices
references/common/assertions.md - Read component-specific files:
- Services:
references/nestjs/services.md - Controllers:
references/nestjs/controllers.md - Guards:
references/nestjs/guards.md - Interceptors:
references/nestjs/interceptors.md - Pipes/Filters:
references/nestjs/pipes-filters.md
- Services:
- 强制要求:阅读- AAA模式、命名规范、覆盖率要求
references/common/rules.md - 阅读- 断言最佳实践
references/common/assertions.md - 阅读对应组件的文件:
- 服务:
references/nestjs/services.md - 控制器:
references/nestjs/controllers.md - 守卫:
references/nestjs/guards.md - 拦截器:
references/nestjs/interceptors.md - 管道/过滤器:
references/nestjs/pipes-filters.md
- 服务:
Setup Mocking
配置模拟
- Read - DeepMocked patterns
references/mocking/deep-mocked.md - Read - Native Jest patterns
references/mocking/jest-native.md - Read - Test data factories
references/mocking/factories.md
- 阅读- DeepMocked模式
references/mocking/deep-mocked.md - 阅读- Jest原生模拟模式
references/mocking/jest-native.md - 阅读- 测试数据工厂
references/mocking/factories.md
Test Repositories
测试仓库
- MongoDB:
references/repository/mongodb.md - PostgreSQL:
references/repository/postgres.md
- MongoDB:
references/repository/mongodb.md - PostgreSQL:
references/repository/postgres.md
Test Kafka (NestJS Microservices)
测试Kafka(NestJS微服务)
- Read - ClientKafka mocking, @MessagePattern/@EventPattern handlers, emit/send testing
references/kafka/kafka.md
- 阅读- ClientKafka模拟、@MessagePattern/@EventPattern处理器、emit/send测试
references/kafka/kafka.md
Test Redis
测试Redis
- Read - Cache operations, health checks, graceful degradation
references/redis/redis.md
- 阅读- 缓存操作、健康检查、优雅降级
references/redis/redis.md
Examples
示例
- Read for comprehensive patterns
references/common/examples.md
- 阅读获取综合模式示例
references/common/examples.md
Optimize Test Performance
优化测试性能
- Read - Worker config, caching, CI optimization
references/common/performance-optimization.md - Read - Fix open handles preventing clean exit
references/common/detect-open-handles.md
- 阅读- 工作线程配置、缓存、CI优化
references/common/performance-optimization.md - 阅读- 修复阻止程序正常退出的未关闭句柄
references/common/detect-open-handles.md
Debug Open Handles
调试未关闭句柄
- Read - Detection commands, common handle types, cleanup patterns
references/common/detect-open-handles.md
- 阅读- 检测命令、常见句柄类型、清理模式
references/common/detect-open-handles.md
Core Principles
核心原则
0. Context Efficiency (Temp File Output)
0. 上下文效率(临时文件输出)
ALWAYS redirect unit test output to temp files, NOT console. Test output can be verbose and bloats agent context.
IMPORTANT: Use unique session ID in filenames to prevent conflicts when multiple agents run.
bash
undefined必须将单元测试输出重定向到临时文件,而非控制台。测试输出可能非常冗长,会占用过多Agent上下文。
重要提示:在文件名中使用唯一会话ID,避免多个Agent运行时发生冲突。
bash
undefinedInitialize session (once at start of testing session)
初始化会话(测试会话开始时执行一次)
export UT_SESSION=$(date +%s)-$$
export UT_SESSION=$(date +%s)-$$
Standard pattern - redirect output to temp file (NO console output)
标准模式 - 将输出重定向到临时文件(不要输出到控制台)
npm test > /tmp/ut-${UT_SESSION}-output.log 2>&1
npm test > /tmp/ut-${UT_SESSION}-output.log 2>&1
Read summary only (last 50 lines)
仅读取摘要(最后50行)
tail -50 /tmp/ut-${UT_SESSION}-output.log
tail -50 /tmp/ut-${UT_SESSION}-output.log
Get failure details
获取失败详情
grep -B 2 -A 15 "FAIL|✕" /tmp/ut-${UT_SESSION}-output.log
grep -B 2 -A 15 "FAIL|✕" /tmp/ut-${UT_SESSION}-output.log
Cleanup when done
完成后清理
rm -f /tmp/ut-${UT_SESSION}-.log /tmp/ut-${UT_SESSION}-.md
**Temp Files** (with `${UT_SESSION}` unique per agent):
- `/tmp/ut-${UT_SESSION}-output.log` - Full test output
- `/tmp/ut-${UT_SESSION}-failures.md` - Tracking file for one-by-one fixing
- `/tmp/ut-${UT_SESSION}-debug.log` - Debug runs
- `/tmp/ut-${UT_SESSION}-verify.log` - Verification runs
- `/tmp/ut-${UT_SESSION}-coverage.log` - Coverage outputrm -f /tmp/ut-${UT_SESSION}-.log /tmp/ut-${UT_SESSION}-.md
**临时文件**(每个Agent的`${UT_SESSION}`唯一):
- `/tmp/ut-${UT_SESSION}-output.log` - 完整测试输出
- `/tmp/ut-${UT_SESSION}-failures.md` - 逐个修复的跟踪文件
- `/tmp/ut-${UT_SESSION}-debug.log` - 调试运行日志
- `/tmp/ut-${UT_SESSION}-verify.log` - 验证运行日志
- `/tmp/ut-${UT_SESSION}-coverage.log` - 覆盖率输出1. AAA Pattern (Mandatory)
1. AAA模式(强制要求)
ALL unit tests MUST follow Arrange-Act-Assert:
typescript
it('should return user when found', async () => {
// Arrange
const userId = 'user-123';
mockRepository.findById.mockResolvedValue({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
// Act
const result = await target.getUser(userId);
// Assert
expect(result).toEqual({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
expect(mockRepository.findById).toHaveBeenCalledWith(userId);
});所有单元测试必须遵循Arrange-Act-Assert(准备-执行-断言)模式:
typescript
it('should return user when found', async () => {
// Arrange(准备)
const userId = 'user-123';
mockRepository.findById.mockResolvedValue({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
// Act(执行)
const result = await target.getUser(userId);
// Assert(断言)
expect(result).toEqual({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
expect(mockRepository.findById).toHaveBeenCalledWith(userId);
});2. Use target
for SUT
target2. 使用target
指代被测系统(SUT)
targetAlways name the system under test as :
targettypescript
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;始终将被测系统命名为:
targettypescript
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;3. DeepMocked Pattern
3. DeepMocked模式
Use for type-safe mocks:
@golevelup/ts-jesttypescript
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});使用实现类型安全的模拟:
@golevelup/ts-jesttypescript
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});4. Specific Assertions
4. 具体断言
Assert exact values, not just existence:
typescript
// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// CORRECT
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});断言精确值,而非仅断言存在性:
typescript
// 错误示例
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// 正确示例
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});5. Mock All Dependencies
5. 模拟所有依赖
Mock external services, never real databases for unit tests:
typescript
// Unit Test: Mock repository
{ provide: UserRepository, useValue: mockRepository }
// Repository Test: Use in-memory database
const mongoServer = await createMongoMemoryServer();模拟外部服务,单元测试中绝不使用真实数据库:
typescript
// 单元测试:模拟仓库
{ provide: UserRepository, useValue: mockRepository }
// 仓库测试:使用内存数据库
const mongoServer = await createMongoMemoryServer();Standard Test Template
标准测试模板
typescript
import { Test, TestingModule } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { MockLoggerService } from 'src/shared/logger/services/mock-logger.service';
describe('UserService', () => {
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
beforeEach(async () => {
// Arrange: Create mocks
mockRepository = createMock<UserRepository>();
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserRepository, useValue: mockRepository },
],
})
.setLogger(new MockLoggerService())
.compile();
target = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getUser', () => {
it('should return user when found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue({
id: 'user-123',
email: 'test@example.com',
});
// Act
const result = await target.getUser('user-123');
// Assert
expect(result).toEqual({ id: 'user-123', email: 'test@example.com' });
});
it('should throw NotFoundException when user not found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue(null);
// Act & Assert
await expect(target.getUser('invalid')).rejects.toThrow(NotFoundException);
});
});
});typescript
import { Test, TestingModule } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { MockLoggerService } from 'src/shared/logger/services/mock-logger.service';
describe('UserService', () => {
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
beforeEach(async () => {
// 准备:创建模拟
mockRepository = createMock<UserRepository>();
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserRepository, useValue: mockRepository },
],
})
.setLogger(new MockLoggerService())
.compile();
target = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getUser', () => {
it('should return user when found', async () => {
// 准备
mockRepository.findById.mockResolvedValue({
id: 'user-123',
email: 'test@example.com',
});
// 执行
const result = await target.getUser('user-123');
// 断言
expect(result).toEqual({ id: 'user-123', email: 'test@example.com' });
});
it('should throw NotFoundException when user not found', async () => {
// 准备
mockRepository.findById.mockResolvedValue(null);
// 执行 & 断言
await expect(target.getUser('invalid')).rejects.toThrow(NotFoundException);
});
});
});Test Coverage Requirements
测试覆盖率要求
| Category | Priority | Description |
|---|---|---|
| Happy path | MANDATORY | Valid inputs producing expected outputs |
| Edge cases | MANDATORY | Empty arrays, null values, boundaries |
| Error cases | MANDATORY | Not found, validation failures |
| Exception behavior | MANDATORY | Correct type, error code, message |
| Business rules | MANDATORY | Domain logic, calculations |
| Input validation | MANDATORY | Invalid inputs, type mismatches |
Coverage Target: 80%+ for new code
| 类别 | 优先级 | 描述 |
|---|---|---|
| 正常流程 | 强制要求 | 有效输入产生预期输出 |
| 边缘场景 | 强制要求 | 空数组、null值、边界值 |
| 错误场景 | 强制要求 | 未找到、验证失败 |
| 异常行为 | 强制要求 | 正确的异常类型、错误码、消息 |
| 业务规则 | 强制要求 | 领域逻辑、计算逻辑 |
| 输入验证 | 强制要求 | 无效输入、类型不匹配 |
覆盖率目标: 新代码覆盖率≥80%
Failure Resolution Protocol
失败修复流程
CRITICAL: Fix ONE test at a time. NEVER run full suite repeatedly while fixing.
When unit tests fail:
- Initialize session (once at start):
bash
export UT_SESSION=$(date +%s)-$$ - Create tracking file: with all failing tests
/tmp/ut-${UT_SESSION}-failures.md - Select ONE failing test - work on only this test
- Run ONLY that test (never full suite):
bash
npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1 tail -50 /tmp/ut-${UT_SESSION}-debug.log - Fix the issue - analyze error, make targeted fix
- Verify fix - run same test 3-5 times:
bash
for i in {1..5}; do npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-run$i.log 2>&1 && echo "Run $i: PASS" || echo "Run $i: FAIL"; done - Mark as FIXED in tracking file
- Move to next failing test - repeat steps 3-7
- Run full suite ONLY ONCE after ALL individual tests pass
- Cleanup:
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
WHY: Running full suite wastes time and context. Each failing test pollutes output, making debugging harder.
关键提示:一次只修复一个测试。修复过程中绝不要反复运行完整测试套件。
当单元测试失败时:
- 初始化会话(开始时执行一次):
bash
export UT_SESSION=$(date +%s)-$$ - 创建跟踪文件:,记录所有失败的测试
/tmp/ut-${UT_SESSION}-failures.md - 选择一个失败测试 - 仅处理该测试
- 仅运行该测试(绝不运行完整套件):
bash
npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1 tail -50 /tmp/ut-${UT_SESSION}-debug.log - 修复问题 - 分析错误,进行针对性修复
- 验证修复 - 重复运行该测试3-5次:
bash
for i in {1..5}; do npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-run$i.log 2>&1 && echo "Run $i: PASS" || echo "Run $i: FAIL"; done - 在跟踪文件中标记为已修复
- 处理下一个失败测试 - 重复步骤3-7
- 所有单个测试通过后,仅运行一次完整套件
- 清理:
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
原因:运行完整套件会浪费时间和上下文,每个失败测试的输出会干扰调试,增加问题定位难度。
Naming Conventions
命名规范
Test Files
测试文件
- Pattern:
*.spec.ts - Location: Co-located with source file
- 命名模式:
*.spec.ts - 位置:与源文件同目录
Test Structure
测试结构
typescript
describe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});typescript
describe('ClassName', () => {
describe('methodName', () => {
it('should [预期行为] when [条件]', () => {});
});
});Variable Names
变量命名
| Variable | Convention |
|---|---|
| SUT | |
| Mocks | |
| Mock Type | |
| 变量 | 规范 |
|---|---|
| 被测系统(SUT) | |
| 模拟对象 | 前缀为 |
| 模拟类型 | |
What NOT to Unit Test
无需进行单元测试的内容
Do NOT create unit tests for:
- Interfaces - Type definitions only, no runtime behavior
- Enums - Static value mappings, no logic to test
- Constants - Static values, no behavior
- Type aliases - Type definitions only
- Plain DTOs - Data structures without logic
Only test files containing executable logic (classes with methods, functions with behavior).
请勿为以下内容创建单元测试:
- 接口 - 仅为类型定义,无运行时行为
- 枚举 - 静态值映射,无逻辑需测试
- 常量 - 静态值,无行为
- 类型别名 - 仅为类型定义
- 普通DTO - 无逻辑的数据结构
仅为包含可执行逻辑的文件编写测试(含方法的类、有行为的函数)。
Anti-Patterns to Avoid
需避免的反模式
| Don't | Why | Do Instead |
|---|---|---|
| Assert only existence | Doesn't catch wrong values | Assert specific values |
| Conditional assertions | Non-deterministic | Separate test cases |
| Test private methods | Couples to implementation | Test via public interface |
| Share state between tests | Causes flaky tests | Fresh setup in beforeEach |
| Mock repositories in services | Tests implementation | Mock interfaces |
| Skip mock verification | Doesn't validate behavior | Verify mock calls |
| Test interfaces/enums/constants | No behavior to test | Skip these files |
| 不要做 | 原因 | 正确做法 |
|---|---|---|
| 仅断言存在性 | 无法捕获值错误 | 断言具体值 |
| 条件断言 | 测试结果不确定 | 拆分测试用例 |
| 测试私有方法 | 与实现细节耦合 | 通过公共接口测试 |
| 测试间共享状态 | 导致测试不稳定 | 在beforeEach中重新初始化 |
| 在服务测试中模拟仓库 | 测试实现细节 | 模拟接口 |
| 跳过模拟调用验证 | 无法验证行为正确性 | 验证模拟调用 |
| 测试接口/枚举/常量 | 无行为需测试 | 跳过这些文件 |
Checklist
检查清单
Setup:
- Use for system under test
target - Use prefix for all mocks
mock - Use type
DeepMocked<T> - Include
.setLogger(new MockLoggerService()) - Follow AAA pattern with comments
- Reset mocks in
afterEach
Coverage:
- Happy path tests for all public methods
- Edge case tests (empty, null, boundaries)
- Error case tests (not found, validation failures)
- Exception type and error code verification
- Mock call verification (parameters + count)
Quality:
- 80%+ coverage on new code
- No assertions on log calls
- No test interdependence
- Tests fail when any field differs
配置:
- 使用指代被测系统
target - 所有模拟对象使用前缀
mock - 使用类型
DeepMocked<T> - 包含
.setLogger(new MockLoggerService()) - 带注释遵循AAA模式
- 在中重置模拟
afterEach
覆盖率:
- 所有公共方法的正常流程测试
- 边缘场景测试(空值、边界值等)
- 错误场景测试(未找到、验证失败等)
- 异常类型和错误码验证
- 模拟调用验证(参数+调用次数)
质量:
- 新代码覆盖率≥80%
- 不对日志调用进行断言
- 测试间无依赖
- 任何字段不一致时测试都会失败