typescript-unit-testing by bmad-labs/skills
npx skills add https://github.com/bmad-labs/skills --skill typescript-unit-testing广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 修复失败的测试 |
| 测试失败、需要诊断时 |
| Optimizing | 提高测试性能 | 测试缓慢、需要维护时 |
重要提示:在开始任何测试任务之前,请先识别用户的意图并加载相应的工作流。
| 用户说/想要 | 要加载的工作流 | 文件 |
|---|---|---|
| "设置测试"、"配置 Jest"、"为项目添加测试"、"安装测试依赖" | Setup | workflows/setup/workflow.md |
| "编写测试"、"添加测试"、"创建测试"、"测试这个服务/控制器" | Writing | workflows/writing/workflow.md |
| "审查测试"、"检查测试质量"、"审计测试"、"这些测试好吗?" | Reviewing | workflows/reviewing/workflow.md |
| "运行测试"、"执行测试"、"检查测试是否通过"、"显示测试结果" | Running | workflows/running/workflow.md |
| "修复测试"、"调试测试"、"测试失败了"、"为什么这个测试坏了?" | Debugging | workflows/debugging/workflow.md |
| "加速测试"、"优化测试"、"测试很慢"、"修复未关闭的句柄" | Optimizing | workflows/optimizing/workflow.md |
references/ 文件references/
├── 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 # 缓存操作、健康检查、优雅降级
references/common/rules.md - AAA 模式、命名、覆盖率references/common/assertions.md - 断言最佳实践references/nestjs/services.mdreferences/nestjs/controllers.mdreferences/nestjs/guards.mdreferences/nestjs/interceptors.mdreferences/nestjs/pipes-filters.mdreferences/mocking/deep-mocked.md - DeepMocked 模式references/mocking/jest-native.md - 原生 Jest 模式references/mocking/factories.md - 测试数据工厂references/repository/mongodb.mdreferences/repository/postgres.mdreferences/kafka/kafka.md - ClientKafka 模拟、@MessagePattern/@EventPattern 处理器、发送/接收测试references/redis/redis.md - 缓存操作、健康检查、优雅降级references/common/examples.md 获取综合模式references/common/performance-optimization.md - 工作线程配置、缓存、CI 优化references/common/detect-open-handles.md - 修复阻止干净退出的未关闭句柄references/common/detect-open-handles.md - 检测命令、常见句柄类型、清理模式始终将单元测试输出重定向到临时文件,而不是控制台。测试输出可能很冗长,会膨胀代理上下文。
重要提示:在文件名中使用唯一的会话 ID,以防止多个代理运行时发生冲突。
# 初始化会话(在测试会话开始时执行一次)
export UT_SESSION=$(date +%s)-$$
# 标准模式 - 将输出重定向到临时文件(无控制台输出)
npm test > /tmp/ut-${UT_SESSION}-output.log 2>&1
# 仅读取摘要(最后 50 行)
tail -50 /tmp/ut-${UT_SESSION}-output.log
# 获取失败详情
grep -B 2 -A 15 "FAIL\|✕" /tmp/ut-${UT_SESSION}-output.log
# 完成后清理
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
临时文件(每个代理使用唯一的 ${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 - 覆盖率输出所有单元测试必须遵循 Arrange-Act-Assert(准备-执行-断言)模式:
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);
});
target 表示被测系统始终将被测系统命名为 target:
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
使用 @golevelup/ts-jest 进行类型安全的模拟:
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});
断言确切的值,而不仅仅是存在性:
// 错误
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// 正确
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});
模拟外部服务,单元测试中绝不使用真实数据库:
// 单元测试:模拟存储库
{ provide: UserRepository, useValue: mockRepository }
// 存储库测试:使用内存数据库
const mongoServer = await createMongoMemoryServer();
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);
});
});
});
| 类别 | 优先级 | 描述 |
|---|---|---|
| 正常路径 | 强制 | 有效输入产生预期输出 |
| 边界情况 | 强制 | 空数组、空值、边界值 |
| 错误情况 | 强制 | 未找到、验证失败 |
| 异常行为 | 强制 | 正确的类型、错误代码、消息 |
| 业务规则 | 强制 | 领域逻辑、计算 |
| 输入验证 | 强制 | 无效输入、类型不匹配 |
覆盖率目标:新代码达到 80% 以上
关键:一次只修复一个测试。修复时切勿重复运行完整套件。
当单元测试失败时:
初始化会话(在开始时执行一次):
export UT_SESSION=$(date +%s)-$$
创建跟踪文件:/tmp/ut-${UT_SESSION}-failures.md,包含所有失败的测试
选择一个失败的测试 - 仅处理此测试
仅运行该测试(切勿运行完整套件):
npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1
tail -50 /tmp/ut-${UT_SESSION}-debug.log
修复问题 - 分析错误,进行针对性修复
验证修复 - 运行同一测试 3-5 次:
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
原因:运行完整套件会浪费时间和上下文。每个失败的测试都会污染输出,使调试更加困难。
*.spec.tsdescribe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});
| 变量 | 约定 |
|---|---|
| 被测系统 | target |
| 模拟对象 | mock 前缀(mockRepository、mockService) |
| 模拟类型 | DeepMocked<T> |
不要为以下内容创建单元测试:
仅测试包含可执行逻辑的文件(具有方法的类、具有行为的函数)。
| 不要 | 原因 | 应改为 |
|---|---|---|
| 仅断言存在性 | 无法捕获错误的值 | 断言具体的值 |
| 条件断言 | 非确定性 | 分离测试用例 |
| 测试私有方法 | 与实现耦合 | 通过公共接口测试 |
| 在测试之间共享状态 | 导致测试不稳定 | 在 beforeEach 中重新设置 |
| 在服务中模拟存储库 | 测试实现 | 模拟接口 |
| 跳过模拟验证 | 不验证行为 | 验证模拟调用 |
| 测试接口/枚举/常量 | 无行为可测试 | 跳过这些文件 |
设置:
target 表示被测系统mock 前缀DeepMocked<T> 类型.setLogger(new MockLoggerService())afterEach 中重置模拟对象覆盖率:
质量:
每周安装次数
103
代码库
GitHub 星标数
3
首次出现
2026年1月26日
安全审计
安装于
opencode90
claude-code86
github-copilot82
codex81
gemini-cli79
cursor71
Unit testing validates individual functions, methods, and classes in isolation by mocking all external dependencies.
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 |
IMPORTANT : Before starting any testing task, identify the user's intent and load the appropriate workflow.
| User Says / Wants | Workflow to Load | File |
|---|---|---|
| "Set up tests", "configure Jest", "add testing to project", "install test dependencies" | Setup | workflows/setup/workflow.md |
| "Write tests", "add tests", "create tests", "test this service/controller" | Writing | workflows/writing/workflow.md |
| "Review tests", "check test quality", "audit tests", "are these tests good?" | Reviewing | workflows/reviewing/workflow.md |
| "Run tests", "execute tests", "check if tests pass", "show test results" | Running | workflows/running/workflow.md |
references/ files to readreferences/
├── 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 degradation
references/common/rules.md - AAA pattern, naming, coveragereferences/common/assertions.md - Assertion best practicesreferences/nestjs/services.mdreferences/nestjs/controllers.mdreferences/nestjs/guards.mdreferences/nestjs/interceptors.mdreferences/nestjs/pipes-filters.mdreferences/mocking/deep-mocked.md - DeepMocked patternsreferences/mocking/jest-native.md - Native Jest patternsreferences/mocking/factories.md - Test data factoriesreferences/repository/mongodb.mdreferences/repository/postgres.mdreferences/kafka/kafka.md - ClientKafka mocking, @MessagePattern/@EventPattern handlers, emit/send testingreferences/redis/redis.md - Cache operations, health checks, graceful degradationreferences/common/examples.md for comprehensive patternsreferences/common/performance-optimization.md - Worker config, caching, CI optimizationreferences/common/detect-open-handles.md - Fix open handles preventing clean exitreferences/common/detect-open-handles.md - Detection commands, common handle types, cleanup patternsALWAYS 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.
# Initialize session (once at start of testing session)
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
# Read summary only (last 50 lines)
tail -50 /tmp/ut-${UT_SESSION}-output.log
# Get failure details
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 outputALL unit tests MUST follow Arrange-Act-Assert:
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);
});
target for SUTAlways name the system under test as target:
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
Use @golevelup/ts-jest for type-safe mocks:
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});
Assert exact values, not just existence:
// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// CORRECT
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});
Mock external services, never real databases for unit tests:
// Unit Test: Mock repository
{ provide: UserRepository, useValue: mockRepository }
// Repository Test: Use in-memory database
const mongoServer = await createMongoMemoryServer();
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);
});
});
});
| 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
CRITICAL: Fix ONE test at a time. NEVER run full suite repeatedly while fixing.
When unit tests fail:
Initialize session (once at start):
export UT_SESSION=$(date +%s)-$$
Create tracking file : /tmp/ut-${UT_SESSION}-failures.md with all failing tests
Select ONE failing test - work on only this test
Run ONLY that test (never full suite):
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:
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
- repeat steps 3-7
WHY : Running full suite wastes time and context. Each failing test pollutes output, making debugging harder.
*.spec.tsdescribe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});
| Variable | Convention |
|---|---|
| SUT | target |
| Mocks | mock prefix (mockRepository, mockService) |
| Mock Type | DeepMocked<T> |
Do NOT create unit tests for:
Only test files containing executable logic (classes with methods, functions with behavior).
| 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 |
Setup:
target for system under testmock prefix for all mocksDeepMocked<T> type.setLogger(new MockLoggerService())afterEachCoverage:
Quality:
Weekly Installs
103
Repository
GitHub Stars
3
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
opencode90
claude-code86
github-copilot82
codex81
gemini-cli79
cursor71
后端测试指南:API端点、业务逻辑与数据库测试最佳实践
11,800 周安装
企业法律风险评估框架:基于严重性与可能性的风险矩阵与分类指南
1,100 周安装
Valyu API 最佳实践指南:搜索、内容提取、AI 答案与深度研究
1,300 周安装
ralphinho-rfc-pipeline:AI驱动的复杂功能分解与多单元编排工作流工具
1,300 周安装
NanoClaw REPL - AI对话持久化工具,支持会话分支、模型切换与跨会话搜索
1,300 周安装
竞争情报分析工具:生成交互式HTML战卡,深度对比竞争对手功能、定价与定位
1,200 周安装
edit-article AI文章编辑助手 - 智能结构化重写,提升内容清晰度与连贯性
1,300 周安装
| "Fix tests", "debug tests", "tests are failing", "why is this test broken?" | Debugging | workflows/debugging/workflow.md |
| "Speed up tests", "optimize tests", "tests are slow", "fix open handles" | Optimizing | workflows/optimizing/workflow.md |
Run full suite ONLY ONCE after ALL individual tests pass
Cleanup : rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md