⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

9.3.1 测试框架怎么选——Jest 配置:测试框架与断言库

Jest 是 JavaScript 生态中最流行的测试框架,零配置即可使用,功能强大且生态完善。

测试框架对比

特性JestVitestMocha
配置复杂度
速度
TypeScript需 ts-jest原生支持需配置
快照测试内置内置需插件
Mock内置内置需插件
生态最成熟快速成长成熟

推荐:新项目使用 Vitest(更快),已有 Jest 项目继续使用 Jest(迁移成本)。

Jest 完整配置

安装依赖

bash
npm install -D jest ts-jest @types/jest

配置文件

typescript
// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  // 预设:支持 TypeScript
  preset: 'ts-jest',
  
  // 测试环境:Node.js(服务端)或 jsdom(浏览器)
  testEnvironment: 'node',
  
  // 测试文件位置
  roots: ['<rootDir>/src', '<rootDir>/__tests__'],
  
  // 测试文件匹配模式
  testMatch: [
    '**/__tests__/**/*.test.ts',
    '**/__tests__/**/*.spec.ts',
  ],
  
  // 忽略的目录
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
  
  // 路径别名(与 tsconfig.json 保持一致)
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  
  // 测试前执行的文件
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  
  // 覆盖率配置
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/index.ts',
  ],
  coverageThreshold: {
    global: {
      branches: 70,
      functions: 70,
      lines: 70,
      statements: 70,
    },
  },
  
  // 超时时间(毫秒)
  testTimeout: 10000,
  
  // 清理 Mock
  clearMocks: true,
  restoreMocks: true,
};

export default config;

初始化文件

typescript
// jest.setup.ts
import { PrismaClient } from '@prisma/client';

// 全局 Prisma 实例
const prisma = new PrismaClient();

// 测试前连接数据库
beforeAll(async () => {
  await prisma.$connect();
});

// 测试后断开连接
afterAll(async () => {
  await prisma.$disconnect();
});

// 扩展 expect 匹配器(可选)
expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received >= floor && received <= ceiling;
    return {
      message: () =>
        `expected ${received} ${pass ? 'not ' : ''}to be within range ${floor} - ${ceiling}`,
      pass,
    };
  },
});

断言库使用

基础断言

typescript
describe('基础断言', () => {
  // 相等性
  it('toBe - 严格相等', () => {
    expect(1 + 1).toBe(2);
    expect('hello').toBe('hello');
  });

  it('toEqual - 深度相等', () => {
    expect({ a: 1 }).toEqual({ a: 1 });
    expect([1, 2, 3]).toEqual([1, 2, 3]);
  });

  // 真值判断
  it('toBeTruthy / toBeFalsy', () => {
    expect(1).toBeTruthy();
    expect(0).toBeFalsy();
    expect(null).toBeFalsy();
  });

  // 数值比较
  it('数值断言', () => {
    expect(10).toBeGreaterThan(5);
    expect(10).toBeLessThanOrEqual(10);
    expect(0.1 + 0.2).toBeCloseTo(0.3);
  });

  // 字符串匹配
  it('字符串断言', () => {
    expect('hello world').toContain('world');
    expect('hello world').toMatch(/world$/);
  });

  // 数组包含
  it('数组断言', () => {
    expect([1, 2, 3]).toContain(2);
    expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 });
  });
});

异步断言

typescript
describe('异步断言', () => {
  it('使用 async/await', async () => {
    const result = await fetchData();
    expect(result).toBe('data');
  });

  it('使用 resolves', async () => {
    await expect(fetchData()).resolves.toBe('data');
  });

  it('使用 rejects', async () => {
    await expect(fetchBadData()).rejects.toThrow('Error');
  });
});

对象匹配

typescript
describe('对象匹配', () => {
  it('toMatchObject - 部分匹配', () => {
    const user = { id: 1, name: 'John', email: 'john@example.com' };
    
    expect(user).toMatchObject({
      name: 'John',
    });
  });

  it('expect.any - 类型匹配', () => {
    expect({
      id: expect.any(Number),
      name: expect.any(String),
      createdAt: expect.any(Date),
    }).toMatchObject({
      id: 1,
      name: 'John',
      createdAt: new Date(),
    });
  });

  it('expect.objectContaining', () => {
    const response = { success: true, data: { id: 1 }, meta: {} };
    
    expect(response).toEqual(
      expect.objectContaining({
        success: true,
        data: expect.objectContaining({ id: 1 }),
      })
    );
  });
});

测试组织结构

typescript
// 使用 describe 分组
describe('OrderService', () => {
  // 嵌套分组
  describe('createOrder', () => {
    // beforeEach 在每个测试前执行
    beforeEach(() => {
      // 准备测试数据
    });

    // afterEach 在每个测试后执行
    afterEach(() => {
      // 清理数据
    });

    it('应创建正常订单', async () => {
      // 测试逻辑
    });

    it('应拒绝无效订单', async () => {
      // 测试逻辑
    });

    // 跳过测试
    it.skip('待实现的功能', () => {});

    // 只运行这个测试
    it.only('调试中的测试', () => {});
  });
});

运行测试

json
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --runInBand"
  }
}
bash
# 运行所有测试
npm test

# 运行特定文件
npm test -- order.service.test.ts

# 运行匹配的测试
npm test -- --testNamePattern="createOrder"

# 监视模式
npm test -- --watch

本节小结

Jest 是功能完整的测试框架,内置断言库、Mock 和覆盖率统计。配置时注意路径别名与 tsconfig.json 保持一致,使用 setupFilesAfterEnv 进行全局初始化。掌握常用断言方法,让测试代码简洁明了。