---
name: typescript-testing
description: 应用具备仓库感知能力的 TypeScript 测试设计、行为依据、隔离性与 mock 边界标准。用于编写或评审单元测试时。
---

# TypeScript 测试规则

## 前置条件检测

在选择测试框架或命令之前，先检查 `package.json`、锁文件、测试配置以及现有测试的导入方式。仅当配置了 Vitest 时才应用 Vitest 专属规则；否则使用仓库已配置的 TypeScript 测试工具，同时保留以下关于行为、隔离性与依据的规则。如果无法确认可运行的测试工具，请报告已检查的路径以及缺失的命令或配置。

## 测试框架

- **Vitest**：当仓库配置或现有测试选用了 Vitest 时使用
- 测试导入：`import { describe, it, expect, beforeEach, vi } from 'vitest'`
- Mock 创建：使用 `vi.mock()`

## 基本测试策略

### 质量要求
- **回归保护**：将测试集中在关键路径、业务逻辑以及一旦回归就会造成实质影响的行为上。当某个未受保护的行为存在实质性回归风险时，添加测试
- **独立性**：每个测试都能独立运行，不依赖其他测试
- **可复现性**：控制时间、随机性、环境变量与外部 I/O，使相同输入产生相同的可观测结果
- **可读性**：每个测试只描述一种行为，将准备/执行/断言分离，并将测试数据限制为该行为实际用到的值

### 测试类型与范围
1. **单元测试**
   - 验证单个函数或类的行为
   - Mock 所有外部依赖
   - 数量最多，采用细粒度实现

2. **集成测试**
   - 验证多个组件之间的协作
   - 对属于被测行为一部分的进程内真实组件使用真实实现；关于外部 I/O 的处理见“Mock 范围决策”
   - 验证实现主要验收标准或跨越进程内组件边界的流程

3. **跨功能验证**
   - 当新功能触及共享的集成点时，若现有功能失效会破坏主要用户旅程或公开契约，或降低次要可观测行为，则现有功能的连续性成为一项证明义务。在能够暴露其失效的成本最低的边界上加以证明
   - 验证模式：现有功能运行 -> 启用新功能 -> 验证现有功能的连续性
   - 成功标准：保留来源验收标准所指定的响应字段与可观测行为；仅当需求或项目配置定义了处理时间阈值的取值与测量方法时，才应用该阈值
   - 设计为可在 CI/CD 流水线中自动执行

## 测试实现规范

### 目录结构与命名
- 测试位于被测模块旁的 `__tests__/` 目录中
- 测试文件：`{target-file-name}.test.ts`
- 集成测试文件：`{target-file-name}.int.test.ts`
- 测试套件：描述目标功能或场景的名称
- 测试用例：描述预期行为的名称

### 测试代码质量规则

保持每个已提交的测试处于有效状态。当测试保护的是当前行为时应修复它；只有在其对应行为已不再被要求、且源需求或实现契约确认该行为可移除时，才能删除该测试。

## 测试质量标准

### 边界与错误场景覆盖
在覆盖正常路径的同时，包含边界值与错误场景。

### 字面量预期值
使用与实现计算过程无关的预期值：直接将契约的值写成字面量，或从独立的权威 fixture 或规范中获取。若预期值与被测对象使用相同的常量或公式计算得出，即便两者都错了测试依然会通过。当输入由 mock 提供时，只要实现对输入做了转换，预期值就应与 mock 的返回值不同。

### 基于结果的验证
验证结果，而非调用顺序或调用次数。

### 有意义的断言
每个测试都应断言其使用方所依赖的属性，以及该操作所建立的状态，而不仅仅是断言“有返回值”。

### 能力探测的后置条件
用于检查某项功能是否可用的探测，只有在通过使用方边界进行检查，并断言使用方所需的确切属性时才算通过。

命令的退出状态、成功的导入以及对象的存在性只能说明该事物是可访问的，因此应将它们视为探测的前置条件，而将面向使用方的属性放入断言中。

| 探测意图 | 准备阶段的证据（单独不足以证明） | 应改为断言 |
|---|---|---|
| 模块可用 | `import` 解析成功、`expect(mod).toBeDefined()` | 通过使用方的入口点调用导出的函数，并断言其返回值或产生的效果 |
| 命令可用 | 退出码为 0 | 调用方使用的输出、文件或状态变化 |
| 配置已生效 | 配置文件解析成功 | 该配置本应改变的可观测行为 |
| 迁移已执行 | 命令报告成功 | 通过真实引擎查询返回迁移后的数据结构 |

### Mock 范围决策
对于协作关系正被测试的每个进程内组件，使用真实实现。当测试的目标是更高层的行为时，替换直接的外部 I/O 依赖；当外部适配器、查询、迁移或服务契约本身就是测试目标时，使用真实引擎或与生产环境等效的测试实例。进行替换时，仍需断言被测对象发送的请求以及它所接受的响应结构，以确保边界契约得到验证。

### 基于属性的测试（fast-check）
当设计文档的验收标准带有 Property 标注时，使用 `fc.assert(fc.property(...))` 形式的 fast-check。

## Mock 类型安全强制要求

将 mock 的类型限定为被测对象实际使用的接口部分——即 `Pick<T, 'usedMethod'>`——而非完整接口，这样未被使用的方法即便发生变化也不会破坏测试，而被使用的方法发生变化则会。使用 `satisfies` 针对该 picked 类型约束 mock 对象字面量，使多余或命名错误的属性在编译期就会报错。

## 数据层测试

### Mock 无法验证的内容

Mock 验证的是调用模式，因此以下数据层属性在仅使用 mock 的测试中会被漏检：
- Schema 不匹配（表名、列名、数据类型）
- 查询正确性（join、过滤、聚合、分组）
- 数据库约束（NOT NULL、UNIQUE、外键）
- 迁移兼容性（导致代码与 schema 不同步的 schema 变更）

**判定规则**：当这些属性中的某一项本身就是测试目标——包括仓储层或数据访问实现本身——时，应按照下方的分级方案对真实引擎进行验证。当数据访问只是一个依赖而非测试目标时，使用 mock 是正确的做法：例如接收数据的业务逻辑（mock 仓储层，测试服务层）、错误处理路径（连接失败、超时），以及数据层本身不是测试目标的单元测试。

### 真实数据库测试（依赖环境）

针对真实数据库引擎验证数据层正确性的可选方案：
- **容器化数据库**，用于 CI 环境
- **内存数据库**，用于快速反馈（注意：方言差异可能掩盖问题）
- **专用测试数据库**，配合种子数据

按以下顺序选择第一个符合仓库现有依据的选项：
1. 若存在 CI 已配置的数据库测试工具，则使用它。
2. 否则，若可以执行容器，则在容器中使用相同的数据库引擎。
3. 仅当被验证的行为与方言无关时才使用内存数据库；并记录未被验证的方言相关行为。
4. 若仓库已经预置并隔离了专用测试数据库，则使用它。

当以上都不可用而数据层正确性又是测试目标时，应停止并报告缺失的环境前提条件。仅凭 mock 得出的结果不能作为查询、schema、约束或迁移正确性的依据。

### AI 生成代码与 schema 感知

生成的数据访问代码可能在语法上正确，却引用了并不存在的 schema 元素，而基于 mock 的测试无论如何都会通过。因此设计文档应包含明确的 schema 引用，以便评审时可以将文档记录的 schema 与数据访问代码进行交叉核对。
