jest-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jest Testing Skill

Jest测试技能

Core Patterns

核心模式

Basic Test

基础测试

javascript
describe('Calculator', () => {
  let calc;
  beforeEach(() => { calc = new Calculator(); });

  test('adds two numbers', () => {
    expect(calc.add(2, 3)).toBe(5);
  });

  test('throws on division by zero', () => {
    expect(() => calc.divide(10, 0)).toThrow('Division by zero');
  });
});
javascript
describe('Calculator', () => {
  let calc;
  beforeEach(() => { calc = new Calculator(); });

  test('adds two numbers', () => {
    expect(calc.add(2, 3)).toBe(5);
  });

  test('throws on division by zero', () => {
    expect(() => calc.divide(10, 0)).toThrow('Division by zero');
  });
});

Matchers

匹配器

javascript
expect(value).toBe(exact);                 // === strict
expect(value).toEqual(object);             // deep equality
expect(value).toBeTruthy();
expect(value).toBeNull();
expect(value).toBeGreaterThan(3);
expect(value).toBeCloseTo(0.3, 5);
expect(str).toMatch(/regex/);
expect(arr).toContain(item);
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty('name');
expect(obj).toMatchObject({ name: 'Alice' });
expect(() => fn()).toThrow(CustomError);
javascript
expect(value).toBe(exact);                 // === 严格相等
expect(value).toEqual(object);             // 深度相等
expect(value).toBeTruthy();
expect(value).toBeNull();
expect(value).toBeGreaterThan(3);
expect(value).toBeCloseTo(0.3, 5);
expect(str).toMatch(/regex/);
expect(arr).toContain(item);
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty('name');
expect(obj).toMatchObject({ name: 'Alice' });
expect(() => fn()).toThrow(CustomError);

Mocking

Mocking

javascript
// Mock function
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: 'test' });
expect(mockFn).toHaveBeenCalledWith('arg1');
expect(mockFn).toHaveBeenCalledTimes(1);

// Mock module
jest.mock('./database');
const db = require('./database');
db.getUser.mockResolvedValue({ name: 'Alice' });

// Mock with implementation
jest.mock('./api', () => ({
  fetchUsers: jest.fn().mockResolvedValue([{ name: 'Alice' }]),
}));

// Spy
const spy = jest.spyOn(console, 'log').mockImplementation();
expect(spy).toHaveBeenCalledWith('expected');
spy.mockRestore();

// Fake timers
jest.useFakeTimers();
jest.advanceTimersByTime(1000);
jest.useRealTimers();
javascript
// Mock函数
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: 'test' });
expect(mockFn).toHaveBeenCalledWith('arg1');
expect(mockFn).toHaveBeenCalledTimes(1);

// Mock模块
jest.mock('./database');
const db = require('./database');
db.getUser.mockResolvedValue({ name: 'Alice' });

// 自定义Mock实现
jest.mock('./api', () => ({
  fetchUsers: jest.fn().mockResolvedValue([{ name: 'Alice' }]),
}));

// 间谍函数
const spy = jest.spyOn(console, 'log').mockImplementation();
expect(spy).toHaveBeenCalledWith('expected');
spy.mockRestore();

// 虚拟计时器
jest.useFakeTimers();
jest.advanceTimersByTime(1000);
jest.useRealTimers();

Async Testing

异步测试

javascript
test('fetches users', async () => {
  const users = await fetchUsers();
  expect(users).toHaveLength(3);
});

test('resolves with data', () => {
  return expect(fetchData()).resolves.toEqual({ data: 'value' });
});

test('rejects with error', () => {
  return expect(fetchBadData()).rejects.toThrow('not found');
});
javascript
test('fetches users', async () => {
  const users = await fetchUsers();
  expect(users).toHaveLength(3);
});

test('resolves with data', () => {
  return expect(fetchData()).resolves.toEqual({ data: 'value' });
});

test('rejects with error', () => {
  return expect(fetchBadData()).rejects.toThrow('not found');
});

React Component Testing (Testing Library)

React组件测试(Testing Library)

javascript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import LoginForm from './LoginForm';

test('submits login form', async () => {
  const onSubmit = jest.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  fireEvent.change(screen.getByLabelText('Email'), {
    target: { value: 'user@test.com' },
  });
  fireEvent.change(screen.getByLabelText('Password'), {
    target: { value: 'password123' },
  });
  fireEvent.click(screen.getByRole('button', { name: /login/i }));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalledWith({
      email: 'user@test.com', password: 'password123',
    });
  });
});
javascript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import LoginForm from './LoginForm';

test('submits login form', async () => {
  const onSubmit = jest.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  fireEvent.change(screen.getByLabelText('Email'), {
    target: { value: 'user@test.com' },
  });
  fireEvent.change(screen.getByLabelText('Password'), {
    target: { value: 'password123' },
  });
  fireEvent.click(screen.getByRole('button', { name: /login/i }));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalledWith({
      email: 'user@test.com', password: 'password123',
    });
  });
});

Snapshot Testing

快照测试

javascript
test('renders correctly', () => {
  const tree = renderer.create(<Button label="Click" />).toJSON();
  expect(tree).toMatchSnapshot();
});
// Update: jest --updateSnapshot
javascript
test('renders correctly', () => {
  const tree = renderer.create(<Button label="Click" />).toJSON();
  expect(tree).toMatchSnapshot();
});
// 更新快照:jest --updateSnapshot

Anti-Patterns

反模式

BadGoodWhy
expect(x === y).toBe(true)
expect(x).toBe(y)
Better errors
No
await
on async
Always
await
Swallows failures
Snapshot everythingSnapshot UI, assert logicSnapshot fatigue
不良实践推荐实践原因
expect(x === y).toBe(true)
expect(x).toBe(y)
错误提示更清晰
异步操作未使用
await
始终使用
await
避免遗漏失败场景
对所有内容做快照UI做快照,逻辑做断言避免快照疲劳

Quick Reference

快速参考

TaskCommand
Run all
npx jest
Watch
npx jest --watch
Coverage
npx jest --coverage
Update snapshots
npx jest --updateSnapshot
Run file
npx jest tests/calc.test.js
Single test
test.only('name', () => {})
任务命令
运行所有测试
npx jest
监听模式
npx jest --watch
生成覆盖率报告
npx jest --coverage
更新快照
npx jest --updateSnapshot
运行指定文件
npx jest tests/calc.test.js
仅运行单个测试
test.only('name', () => {})

Deep Patterns

进阶模式

For production-grade patterns, see
reference/playbook.md
:
SectionWhat's Inside
§1 Production ConfigNode + React configs, path aliases, coverage thresholds
§2 Mocking Deep DiveModule/partial/manual mocks, spies, timers, env vars
§3 Async PatternsPromises, rejections, event emitters, streams
§4 test.eachArray, tagged template, describe.each for table-driven tests
§5 Custom MatcherstoBeWithinRange, toBeValidEmail, TypeScript declarations
§6 React Testing LibraryuserEvent, hooks, context providers
§7 Snapshot TestingComponent, inline, property matchers
§8 API Service TestingMocked axios, CRUD patterns, error handling
§9 Global SetupMulti-project config, DB setup/teardown
§10 CI/CDGitHub Actions with coverage gates
§11 Debugging Table10 common problems with fixes
§12 Best Practices15-item production checklist
如需生产级别的实践模式,请查看
reference/playbook.md
:
章节内容概述
§1 生产环境配置Node + React配置、路径别名、覆盖率阈值
§2 Mocking深度解析模块/部分/手动Mock、间谍函数、计时器、环境变量
§3 异步模式Promise、异常捕获、事件发射器、流
§4 test.each数组、标签模板、describe.each表格驱动测试
§5 自定义匹配器toBeWithinRange、toBeValidEmail、TypeScript声明
§6 React Testing LibraryuserEvent、hooks、上下文提供者
§7 快照测试组件、内联、属性匹配器
§8 API服务测试Mocked axios、CRUD模式、错误处理
§9 全局配置多项目配置、数据库初始化/清理
§10 CI/CD带覆盖率校验的GitHub Actions
§11 调试指南10个常见问题及解决方案
§12 最佳实践15条生产环境检查清单