2026年5月31日 11:08

Jest 如何测试 TypeScript 项目并配置 ts-jest?

Jest 测 TypeScript 项目时,先要分清两个问题:测试运行时怎么把 TS 转成 JS,以及类型错误由谁负责检查。ts-jest 可以在 Jest 运行时编译 TypeScript,配置直观,适合希望测试和 tsconfig 保持一致的项目。另一个常见选择是 Babel 或 SWC 转译,它们更快,但通常不做完整类型检查。

ts-jest 基础配置怎么写?

先安装 Jest、类型声明和 ts-jest。Node 项目一般使用 node 环境,前端组件或 DOM 工具才需要 jsdom。

bash
npm i -D jest ts-jest @types/jest typescript
js
module.exports = { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['**/?(*.)+(spec|test).ts'], moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'] }

tsconfig.json 里要包含 Jest 类型,否则 describe、test、expect 可能被编辑器标红。

json
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "types": ["jest", "node"] } }

如果项目本身是 ESM,配置会更敏感,需要使用 preset: 'ts-jest/presets/default-esm',并处理 extensionsToTreatAsEsm。这是 ts-jest 里最常见的坑之一。

类型安全的测试怎么写?

测试不应该为了通过而到处 as any。TypeScript 的价值在于让测试数据、Mock 和返回值也遵守业务类型。

ts
export interface User { id: number; name: string } export function formatUser(user: User): string { return `${user.id}:${user.name}` } test('格式化用户信息', () => { const user: User = { id: 1, name: 'Ada' } expect(formatUser(user)).toBe('1:Ada') })

Mock 函数可以用 jest.MockedFunction 或 jest.mocked,避免 mockResolvedValue 的类型丢失。

ts
import { fetchUser } from './api' jest.mock('./api') const mockedFetchUser = jest.mocked(fetchUser) test('加载用户', async () => { mockedFetchUser.mockResolvedValue({ id: 1, name: 'Ada' }) await expect(loadUserName(1)).resolves.toBe('Ada') })

要不要让 Jest 做类型检查?

ts-jest 可以诊断类型错误,但大型项目里会拖慢测试。很多团队会把 tsc --noEmit 放到 CI 的单独步骤,让 Jest 专注行为测试。这样失败信息更清晰:类型错归类型检查,逻辑错归单元测试。

追问

ts-jest、babel-jest 和 swc-jest 怎么选?

ts-jest 最贴近 TypeScript 编译器,路径、装饰器和部分 tsconfig 行为更容易对齐。Babel 或 SWC 通常更快,适合大型前端项目,但它们主要是转译,不负责完整类型检查。取舍是准确性和速度:配置复杂、依赖 TS 编译特性的项目优先 ts-jest;追求测试速度并已有独立 tsc --noEmit 的项目可以选 SWC。踩坑是以为 Jest 通过就代表类型没问题,实际上转译型方案可能放过类型错误。

路径别名为什么在测试里经常失效?

TypeScript 的 paths 只告诉编译器怎么解析,不会自动教 Jest 解析模块。Jest 需要单独配置 moduleNameMapper,或者用 pathsToModuleNameMapper 从 tsconfig 生成。边界是 monorepo 里 rootDir、baseUrl 和包边界更复杂,不能简单复制单包配置。常见坑是源码能编译,测试却报 Cannot find module '@/xxx'。

ESM 项目配置 Jest 有什么坑?

ESM 下 type: module、tsconfig 的 module、Jest preset 和导入扩展名必须互相匹配。很多错误不是业务代码错,而是 CJS/ESM 混用导致模块加载失败。取舍是如果项目没有强 ESM 需求,测试环境保持 CJS 会省事很多;如果库要发布 ESM,就应该尽早把测试跑在接近发布格式的环境里。踩坑是 mock ESM 模块方式和 CJS 不同,旧的 jest.mock 习惯可能失效。

TypeScript 测试里什么时候可以用 as any?

as any 可以用于刻意构造非法输入,测试运行时防御逻辑,例如后端收到脏数据。除此之外应尽量避免,因为它会绕开类型系统,让测试数据变得不可信。取舍是:为了测边界可以局部使用,但要用注释说明这是故意破坏类型。踩坑是为了省事大量 as any,最后测试覆盖了一个现实中根本不会出现的类型形状。

类型测试和行为测试要分开吗?

通常要分开。Jest 擅长测运行时行为,类型层面的断言可以用 tsd、expect-type 或 tsc --noEmit。边界是工具库、SDK、泛型函数这类类型就是产品能力的代码,应该补类型测试。普通业务项目则不必把所有类型都放进 Jest,否则会让测试意图变模糊。

标签:Jest