jest-skill
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJest 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 --updateSnapshotjavascript
test('renders correctly', () => {
const tree = renderer.create(<Button label="Click" />).toJSON();
expect(tree).toMatchSnapshot();
});
// 更新快照:jest --updateSnapshotAnti-Patterns
反模式
| Bad | Good | Why |
|---|---|---|
| | Better errors |
No | Always | Swallows failures |
| Snapshot everything | Snapshot UI, assert logic | Snapshot fatigue |
| 不良实践 | 推荐实践 | 原因 |
|---|---|---|
| | 错误提示更清晰 |
异步操作未使用 | 始终使用 | 避免遗漏失败场景 |
| 对所有内容做快照 | UI做快照,逻辑做断言 | 避免快照疲劳 |
Quick Reference
快速参考
| Task | Command |
|---|---|
| Run all | |
| Watch | |
| Coverage | |
| Update snapshots | |
| Run file | |
| Single test | |
| 任务 | 命令 |
|---|---|
| 运行所有测试 | |
| 监听模式 | |
| 生成覆盖率报告 | |
| 更新快照 | |
| 运行指定文件 | |
| 仅运行单个测试 | |
Deep Patterns
进阶模式
For production-grade patterns, see :
reference/playbook.md| Section | What's Inside |
|---|---|
| §1 Production Config | Node + React configs, path aliases, coverage thresholds |
| §2 Mocking Deep Dive | Module/partial/manual mocks, spies, timers, env vars |
| §3 Async Patterns | Promises, rejections, event emitters, streams |
| §4 test.each | Array, tagged template, describe.each for table-driven tests |
| §5 Custom Matchers | toBeWithinRange, toBeValidEmail, TypeScript declarations |
| §6 React Testing Library | userEvent, hooks, context providers |
| §7 Snapshot Testing | Component, inline, property matchers |
| §8 API Service Testing | Mocked axios, CRUD patterns, error handling |
| §9 Global Setup | Multi-project config, DB setup/teardown |
| §10 CI/CD | GitHub Actions with coverage gates |
| §11 Debugging Table | 10 common problems with fixes |
| §12 Best Practices | 15-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 Library | userEvent、hooks、上下文提供者 |
| §7 快照测试 | 组件、内联、属性匹配器 |
| §8 API服务测试 | Mocked axios、CRUD模式、错误处理 |
| §9 全局配置 | 多项目配置、数据库初始化/清理 |
| §10 CI/CD | 带覆盖率校验的GitHub Actions |
| §11 调试指南 | 10个常见问题及解决方案 |
| §12 最佳实践 | 15条生产环境检查清单 |