mcpField
T
typescript-unit-testing

typescript-unit-testing

github.com/bmad-labs测试与质量19501 次安装6 天前更新
代码仓库

关于 typescript-unit-testing

面向 TypeScript 项目的单元测试技能,按设置、编写、审查、运行、调试和优化等意图选择工作流。提供 NestJS、数据库及消息系统测试参考,并强调隔离依赖、覆盖边界与错误场景。

主要功能

  1. 01按任务选择测试工作流
  2. 02提供 NestJS 测试参考
  3. 03涵盖数据库与消息系统测试
  4. 04规定 AAA 结构与模拟规则
  5. 05指导失败测试逐个排查
  6. 06包含性能与覆盖率要求

适用场景

  1. 01为新项目配置测试基础设施
  2. 02为服务和控制器编写单元测试
  3. 03审查测试质量并补齐边界场景
  4. 04定位失败测试或处理慢测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bmad-labs/skills --skill typescript-unit-testing

安装后在 Claude Code 中输入/typescript-unit-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
references/common/
assertions.md
detect-open-handles.md
examples.md
knowledge.md
performance-optimization.md
rules.md
references/kafka/
kafka.md
references/mocking/
deep-mocked.md
factories.md
jest-native.md
references/nestjs/
controllers.md
guards.md
interceptors.md
pipes-filters.md
services.md
references/redis/
redis.md
references/repository/
mongodb.md
postgres.md
workflows/debugging/
workflow.md
workflows/debugging/steps/
step-01-init.md
step-01b-continue.md
step-02-classify-failure.md
step-03-analyze.md
step-04-implement-fix.md
step-05-verify-fix.md
step-06-document-resolution.md
workflows/debugging/templates/
output-template.md
workflows/optimizing/steps/
step-01-init.md
step-01b-continue.md

详细介绍

单元测试技能

单元测试通过模拟所有外部依赖,隔离验证单个函数、方法和类。


工作流

如需按步骤指导执行单元测试任务,请使用相应的工作流:

工作流用途使用时机
Setup初始化测试基础设施新项目或缺少测试配置
Writing编写新的单元测试为组件创建测试
Reviewing审查现有测试代码审查、质量审计
Running执行测试运行测试、分析结果
Debugging修复失败的测试测试失败,需要诊断
Optimizing提升测试性能测试缓慢、需要改善可维护性

工作流选择指南

IMPORTANT:开始任何测试任务之前,先识别用户意图并加载相应的工作流。

检测用户意图 → 选择工作流
用户表达的需求要加载的工作流文件
"Set up tests", "configure Jest", "add testing to project", "install test dependencies"Setupworkflows/setup/workflow.md
"Write tests", "add tests", "create tests", "test this service/controller"Writingworkflows/writing/workflow.md
"Review tests", "check test quality", "audit tests", "are these tests good?"Reviewingworkflows/reviewing/workflow.md
"Run tests", "execute tests", "check if tests pass", "show test results"Runningworkflows/running/workflow.md
"Fix tests", "debug tests", "tests are failing", "why is this test broken?"Debuggingworkflows/debugging/workflow.md
"Speed up tests", "optimize tests", "tests are slow", "fix open handles"Optimizingworkflows/optimizing/workflow.md
工作流执行协议
  1. ALWAYS load the workflow file first - 采取行动前,先阅读完整的工作流
  2. Follow each step in order - 按顺序完成各项检查点
  3. Load knowledge files as directed - 每个工作流都会指定要阅读哪些 references/ 文件
  4. Verify compliance after completion - 重新阅读相关参考文件,确保质量

知识库结构

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 degradation

按任务分类的快速参考

编写单元测试
  1. MANDATORY:阅读 references/common/rules.md - AAA 模式、命名、覆盖率
  2. 阅读 references/common/assertions.md - 断言最佳实践
  3. 阅读组件专用文件:
    • 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
设置模拟
  1. 阅读 references/mocking/deep-mocked.md - DeepMocked 模式
  2. 阅读 references/mocking/jest-native.md - 原生 Jest 模式
  3. 阅读 references/mocking/factories.md - 测试数据工厂
测试仓库
  1. MongoDB:references/repository/mongodb.md
  2. PostgreSQL:references/repository/postgres.md
测试 Kafka(NestJS 微服务)
  • 阅读 references/kafka/kafka.md - ClientKafka 模拟、@MessagePattern/@EventPattern 处理程序、emit/send 测试
测试 Redis
  • 阅读 references/redis/redis.md - 缓存操作、健康检查、优雅降级
示例
  • 阅读 references/common/examples.md,了解全面的模式示例
优化测试性能
  1. 阅读 references/common/performance-optimization.md - Worker 配置、缓存、CI 优化
  2. 阅读 references/common/detect-open-handles.md - 修复阻止进程正常退出的未关闭句柄
调试未关闭句柄
  • 阅读 references/common/detect-open-handles.md - 检测命令、常见句柄类型、清理模式

核心原则

0. 上下文效率(输出到临时文件)

始终将单元测试输出重定向到临时文件,而不是控制台。测试输出可能很冗长,会占用大量 Agent 上下文。

重要:在文件名中使用唯一的会话 ID,以避免多个 Agent 并行运行时发生冲突。

# 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

临时文件(每个 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 模式(必需)

所有单元测试都必须遵循 Arrange-Act-Assert(准备-执行-断言):

it('should return user when found', async () => {
  // Arrange
  const userId = 'user-123';
  mockRepository.findById.mockResolvedValue({
    id: userId,
    email: '[email protected]',
    name: 'Test User',
  });

  // Act
  const result = await target.getUser(userId);

  // Assert
  expect(result).toEqual({
    id: userId,
    email: '[email protected]',
    name: 'Test User',
  });
  expect(mockRepository.findById).toHaveBeenCalledWith(userId);
});
2. 使用 target 表示 SUT

始终将被测系统命名为 target:

let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
3. DeepMocked 模式

使用 @golevelup/ts-jest 创建类型安全的模拟对象:

import { createMock, DeepMocked } from '@golevelup/ts-jest';

let mockService: DeepMocked<UserService>;

beforeEach(() => {
  mockService = createMock<UserService>();
});
4. 明确断言

断言确切的值,而不只是断言其存在:

// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();

// CORRECT
expect(result).toEqual({
  id: 'user-123',
  email: '[email protected]',
  name: 'Test User',
});
5. 模拟所有依赖项

模拟外部服务,单元测试中绝不使用真实数据库:

// 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: '[email protected]',
      });

      // Act
      const result = await target.getUser('user-123');

      // Assert
      expect(result).toEqual({ id: 'user-123', email: '[email protected]' });
    });

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

      // Act & Assert
      await expect(target.getUser('invalid')).rejects.toThrow(NotFoundException);
    });
  });
});

测试覆盖率要求

类别优先级说明
正常路径必须输入有效并产生预期输出
边界情况必须空数组、null 值、边界值
错误情况必须未找到、验证失败
异常行为必须正确的类型、错误代码和消息
业务规则必须领域逻辑、计算
输入验证必须无效输入、类型不匹配

覆盖率目标: 新代码达到 80% 以上


失败处理流程

关键:一次修复一个测试。修复过程中绝不要反复运行完整测试套件。

单元测试失败时:

  1. 初始化会话(开始时执行一次):
    export UT_SESSION=$(date +%s)-$$
    
  2. 创建跟踪文件:/tmp/ut-${UT_SESSION}-failures.md,记录所有失败的测试
  3. 选择一个失败的测试 - 只处理这一个测试
  4. 仅运行该测试(绝不运行完整测试套件):
    npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1
    tail -50 /tmp/ut-${UT_SESSION}-debug.log
    
  5. 修复问题 - 分析错误并进行针对性修复
  6. 验证修复 - 将同一测试运行 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
    
  7. 在跟踪文件中将其标记为 FIXED
  8. 处理下一个失败的测试 - 重复步骤 3-7
  9. 只有在所有单个测试都通过后,才运行一次完整测试套件
  10. 清理:rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md

原因:运行完整测试套件会浪费时间和上下文。每个失败的测试都会让输出变得冗长,使调试更加困难。


命名约定

测试文件
  • 模式:*.spec.ts
  • 位置:与源文件放在同一目录
测试结构
describe('ClassName', () => {
  describe('methodName', () => {
    it('should [expected behavior] when [condition]', () => {});
  });
});
变量名
变量约定
SUTtarget
模拟对象使用 mock 前缀(mockRepository、mockService)
模拟类型DeepMocked<T>

不应编写单元测试的内容

不要为以下内容创建单元测试:

  • 接口 - 仅包含类型定义,没有运行时行为
  • 枚举 - 静态值映射,没有可测试的逻辑
  • 常量 - 静态值,没有行为
  • 类型别名 - 仅包含类型定义
  • 简单 DTO - 不含逻辑的数据结构

只测试包含可执行逻辑的文件(带有方法的类、包含行为的函数)。


应避免的反模式

不要这样做原因改为这样做
只断言存在性无法发现错误的值断言具体值
使用条件断言结果不确定分开编写测试用例
测试私有方法与实现细节耦合通过公共接口测试
在测试之间共享状态会导致测试不稳定在 beforeEach 中重新初始化
在服务中模拟存储库测试的是实现细节模拟接口
跳过模拟验证无法验证行为验证模拟调用
测试接口、枚举或常量没有可测试的行为跳过这些文件

检查清单

设置:

  • 对被测系统使用 target
  • 所有模拟对象都使用 mock 前缀
  • 使用 DeepMocked<T> 类型
  • 包含 .setLogger(new MockLoggerService())
  • 遵循 AAA 模式并添加注释
  • 在 afterEach 中重置模拟对象

覆盖率:

  • 为所有公共方法编写正常路径测试
  • 编写边界情况测试(空值、null、边界)
  • 编写错误情况测试(未找到、验证失败)
  • 验证异常类型和错误代码
  • 验证模拟调用(参数 + 次数)

质量:

  • 新代码的覆盖率达到 80% 以上
  • 不对日志调用进行断言
  • 测试之间互不依赖
  • 任意字段不同时,测试都会失败

评论

支持 Markdown