17.2 单元测试(Jest)

17.2 单元测试(Jest)

单元测试是现代 JavaScript 工具库开发中不可或缺的环节,Jest 是当前最流行的测试框架之一。本节将详细介绍如何使用 Jest 为你的工具库建立完整的测试体系。

为什么选择 Jest?

  1. 零配置:开箱即用,大部分场景无需额外配置
  2. 快速交互:智能监控文件变化,只运行相关测试
  3. 快照测试:轻松验证 UI 或复杂数据结构
  4. 强大 Mock:完整的模拟系统
  5. 覆盖率报告:内置覆盖率统计功能

基础环境搭建

首先安装 Jest:

npm install --save-dev jest @types/jest

package.json 中添加测试脚本:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

基本测试结构

创建 __tests__ 目录或在文件旁创建 .test.js 文件:

// math.utils.js
export function sum(a, b) {
  return a + b;
}

// math.utils.test.js
import { sum } from '../math.utils';

describe('Math Utilities', () => {
  test('adds two numbers correctly', () => {
    expect(sum(1, 2)).toBe(3);
  });

  test('handles decimal numbers', () => {
    expect(sum(0.1, 0.2)).toBeCloseTo(0.3);
  });
});

常用断言方法

断言方法 用途
toBe() 严格相等(===)
toEqual() 深度递归比较对象
toBeTruthy() 检查是否为真值
toHaveLength() 检查数组/字符串长度
toThrow() 检查是否抛出错误
toMatchSnapshot() 快照测试
toHaveBeenCalledWith() 检查函数调用参数

测试异步代码

  1. Promise 测试
test('fetches data successfully', () => {
  return fetchData().then(data => {
    expect(data).toHaveProperty('success', true);
  });
});
  1. async/await 测试
test('fetches data with async/await', async () => {
  const data = await fetchData();
  expect(data.user).toEqual('admin');
});
  1. 回调函数测试
test('callback style test', done => {
  fetchData(data => {
    expect(data).toBeDefined();
    done(); // 必须调用 done()
  });
});

Mock 技术深度应用

  1. 函数 Mock
const mockFn = jest.fn();
mockFn('arg1');
expect(mockFn).toHaveBeenCalledWith('arg1');
  1. 模块 Mock
jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve({ data: 'mock data' }))
}));
  1. 定时器 Mock
jest.useFakeTimers();
setTimeout(() => console.log('done'), 1000);
jest.advanceTimersByTime(1000); // 快进时间

高级测试技巧

  1. 参数化测试
describe.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 2, 4]
])('add(%i, %i)', (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });
});
  1. 生命周期钩子
beforeAll(() => console.log('在所有测试之前运行'));
afterEach(() => console.log('在每个测试之后运行'));
  1. 测试环境隔离
describe('database', () => {
  beforeAll(() => setupTestDatabase());
  afterAll(() => tearDownTestDatabase());
});

测试覆盖率配置

jest.config.js 中配置:

module.exports = {
  collectCoverage: true,
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  coveragePathIgnorePatterns: [
    '/node_modules/',
    '/__tests__/',
    'index.js'
  ]
};

与 TypeScript 集成

  1. 安装依赖:
npm install --save-dev ts-jest @types/jest
  1. 配置 jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  globals: {
    'ts-jest': {
      diagnostics: false
    }
  }
};

测试工具库的特别考量

  1. 浏览器 API 模拟
beforeEach(() => {
  window.localStorage = {
    store: {},
    getItem(key) {
      return this.store[key] || null;
    },
    setItem(key, value) {
      this.store[key] = String(value);
    },
    clear() {
      this.store = {};
    }
  };
});
  1. 性能测试
test('should execute in less than 100ms', () => {
  const start = performance.now();
  heavyCalculation();
  const end = performance.now();
  expect(end - start).toBeLessThan(100);
});

常见问题解决方案

  1. ES Module 导入问题
// jest.config.js
module.exports = {
  transform: {
    '^.+\\.js$': 'babel-jest'
  }
};
  1. CSS/静态资源处理
module.exports = {
  moduleNameMapper: {
    '\\.(css|less)$': 'identity-obj-proxy',
    '\\.(jpg|png)$': '<rootDir>/__mocks__/fileMock.js'
  }
};
  1. 环境变量问题
// jest.setup.js
process.env.API_URL = 'http://test.example.com';

最佳实践建议

  1. 测试命名规范

    • 测试文件:[name].test.js[name].spec.js
    • 测试描述:describe('模块/功能名称')
    • 测试用例:test('应该...当...')
  2. 测试组织原则

    • 每个测试只验证一个行为
    • 避免测试内部实现细节
    • 优先测试公共接口
  3. 测试数据管理

    • 使用工厂函数生成测试数据
    • 考虑使用 Faker.js 生成随机数据
    • 对大型测试数据使用 beforeAll 共享

通过以上配置和实践,你可以为你的工具库建立完善的测试体系。接下来我们将学习如何将你的库发布到 npm 仓库。

#前端开发 分享于 2025-03-25

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】