Jest Testing Skill
Core Patterns
Basic Test
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
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);
Mocking
// 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();
Async Testing
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)
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import LoginForm from './LoginForm';
test('submits login form', async () => {
const
render(<LoginForm />);
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
test('renders correctly', () => {
const tree = renderer.create(<Button label="Click" />).toJSON();
expect(tree).toMatchSnapshot();
});
// Update: jest --updateSnapshot
Anti-Patterns
| Bad |
Good |
Why |
expect(x === y).toBe(true) |
expect(x).toBe(y) |
Better errors |
No await on async |
Always await |
Swallows failures |
| Snapshot everything |
Snapshot UI, assert logic |
Snapshot fatigue |
Quick Reference
| Task |
Command |
| 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', () => {}) |
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 |
1---2name: jest-skill3description: Generates Jest unit and integration tests in JavaScript or TypeScript. Covers mocking, snapshots, async testing, and React component testing. Use when user mentions "Jest", "describe/it/expect", "jest.mock", "toMatchSnapshot". Triggers on: "Jest", "expect().toBe()", "jest.mock", "snapshot test", "JS test", "React test".4license: MIT5---6
7# Jest Testing Skill
8
9## Core Patterns
10
11### Basic Test
12
13```javascript
14describe('Calculator', () => {
15 let calc;
16 beforeEach(() => { calc = new Calculator(); });
17
18 test('adds two numbers', () => {
19 expect(calc.add(2, 3)).toBe(5);
20 });
21
22 test('throws on division by zero', () => {
23 expect(() => calc.divide(10, 0)).toThrow('Division by zero');
24 });
25});
26```
27
28### Matchers
29
30```javascript
31expect(value).toBe(exact); // === strict
32expect(value).toEqual(object); // deep equality
33expect(value).toBeTruthy();
34expect(value).toBeNull();
35expect(value).toBeGreaterThan(3);
36expect(value).toBeCloseTo(0.3, 5);
37expect(str).toMatch(/regex/);
38expect(arr).toContain(item);
39expect(arr).toHaveLength(3);
40expect(obj).toHaveProperty('name');
41expect(obj).toMatchObject({ name: 'Alice' });
42expect(() => fn()).toThrow(CustomError);
43```
44
45### Mocking
46
47```javascript
48// Mock function
49const mockFn = jest.fn();
50mockFn.mockReturnValue(42);
51mockFn.mockResolvedValue({ data: 'test' });
52expect(mockFn).toHaveBeenCalledWith('arg1');
53expect(mockFn).toHaveBeenCalledTimes(1);
54
55// Mock module
56jest.mock('./database');
57const db = require('./database');
58db.getUser.mockResolvedValue({ name: 'Alice' });
59
60// Mock with implementation
61jest.mock('./api', () => ({
62 fetchUsers: jest.fn().mockResolvedValue([{ name: 'Alice' }]),
63}));
64
65// Spy
66const spy = jest.spyOn(console, 'log').mockImplementation();
67expect(spy).toHaveBeenCalledWith('expected');
68spy.mockRestore();
69
70// Fake timers
71jest.useFakeTimers();
72jest.advanceTimersByTime(1000);
73jest.useRealTimers();
74```
75
76### Async Testing
77
78```javascript
79test('fetches users', async () => {
80 const users = await fetchUsers();
81 expect(users).toHaveLength(3);
82});
83
84test('resolves with data', () => {
85 return expect(fetchData()).resolves.toEqual({ data: 'value' });
86});
87
88test('rejects with error', () => {
89 return expect(fetchBadData()).rejects.toThrow('not found');
90});
91```
92
93### React Component Testing (Testing Library)
94
95```javascript
96import { render, screen, fireEvent, waitFor } from '@testing-library/react';
97import '@testing-library/jest-dom';
98import LoginForm from './LoginForm';
99
100test('submits login form', async () => {
101 const onSubmit = jest.fn();
102 render(<LoginForm onSubmit={onSubmit} />);
103
104 fireEvent.change(screen.getByLabelText('Email'), {
105 target: { value: 'user@test.com' },
106 });
107 fireEvent.change(screen.getByLabelText('Password'), {
108 target: { value: 'password123' },
109 });
110 fireEvent.click(screen.getByRole('button', { name: /login/i }));
111
112 await waitFor(() => {
113 expect(onSubmit).toHaveBeenCalledWith({
114 email: 'user@test.com', password: 'password123',
115 });
116 });
117});
118```
119
120### Snapshot Testing
121
122```javascript
123test('renders correctly', () => {
124 const tree = renderer.create(<Button label="Click" />).toJSON();
125 expect(tree).toMatchSnapshot();
126});
127// Update: jest --updateSnapshot
128```
129
130### Anti-Patterns
131
132| Bad | Good | Why |
133|-----|------|-----|
134| `expect(x === y).toBe(true)` | `expect(x).toBe(y)` | Better errors |
135| No `await` on async | Always `await` | Swallows failures |
136| Snapshot everything | Snapshot UI, assert logic | Snapshot fatigue |
137
138## Quick Reference
139
140| Task | Command |
141|------|---------|
142| Run all | `npx jest` |
143| Watch | `npx jest --watch` |
144| Coverage | `npx jest --coverage` |
145| Update snapshots | `npx jest --updateSnapshot` |
146| Run file | `npx jest tests/calc.test.js` |
147| Single test | `test.only('name', () => {})` |
148
149## Deep Patterns
150
151For production-grade patterns, see `reference/playbook.md`:
152
153| Section | What's Inside |
154|---------|--------------|
155| §1 Production Config | Node + React configs, path aliases, coverage thresholds |
156| §2 Mocking Deep Dive | Module/partial/manual mocks, spies, timers, env vars |
157| §3 Async Patterns | Promises, rejections, event emitters, streams |
158| §4 test.each | Array, tagged template, describe.each for table-driven tests |
159| §5 Custom Matchers | toBeWithinRange, toBeValidEmail, TypeScript declarations |
160| §6 React Testing Library | userEvent, hooks, context providers |
161| §7 Snapshot Testing | Component, inline, property matchers |
162| §8 API Service Testing | Mocked axios, CRUD patterns, error handling |
163| §9 Global Setup | Multi-project config, DB setup/teardown |
164| §10 CI/CD | GitHub Actions with coverage gates |
165| §11 Debugging Table | 10 common problems with fixes |
166| §12 Best Practices | 15-item production checklist |