--- skill_name: jest-patterns skill_category: testing description: Jest testing patterns, anti-patterns, and quality rules allowed_tools: [Read, Edit, Write, Grep, Bash] token_estimate: 1800 version: 1.0 last_updated: 2026-01-13 owner: Claude Copilot status: active tags: [jest, testing, javascript, typescript, unit-test, integration-test, quality] related_skills: [javascript-patterns, react-patterns, pytest-patterns] trigger_files: ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js", "jest.config.*", "__tests__/**"] trigger_keywords: [jest, test, spec, mock, describe, it, expect, beforeEach, afterEach] --- # Jest Patterns Modern Jest testing patterns, anti-patterns, and quality rules for JavaScript/TypeScript. ## Core Principles | Principle | Description | |-----------|-------------| | **Test Behavior** | Test what code does, not how it does it | | **Isolation** | Each test independent, no shared state | | **AAA Pattern** | Arrange → Act → Assert structure | | **Fast Feedback** | Tests run in milliseconds | ## Patterns vs Anti-Patterns ### Test Structure (AAA) ```typescript // GOOD: Clear AAA structure describe('UserService', () => { it('should create user with valid data', async () => { // Arrange const userData = { name: 'John', email: 'john@example.com' }; const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) }; const service = new UserService(mockRepo); // Act const result = await service.createUser(userData); // Assert expect(result.id).toBe('1'); expect(mockRepo.save).toHaveBeenCalledWith(userData); }); }); // BAD: Mixed arrange/act/assert it('creates user', async () => { const service = new UserService({ save: jest.fn() }); expect(await service.createUser({ name: 'John' })).toBeDefined(); // What are we testing? Unclear! }); ``` ### Mocking ```typescript // GOOD: Minimal, focused mocks const mockFetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: 'test' }) }); // GOOD: Reset mocks between tests beforeEach(() => { jest.clearAllMocks(); }); // GOOD: Mock only what's necessary jest.mock('./database', () => ({ query: jest.fn() })); // BAD: Over-mocking implementation details jest.mock('./service', () => ({ __esModule: true, default: jest.fn().mockImplementation(() => ({ method1: jest.fn(), method2: jest.fn(), method3: jest.fn(), // 20 more methods... })) })); ``` ### Assertions ```typescript // GOOD: Specific, meaningful assertions expect(user.name).toBe('John'); expect(errors).toHaveLength(2); expect(result).toEqual({ id: '1', status: 'active' }); expect(callback).toHaveBeenCalledWith('arg1', expect.any(Number)); // GOOD: Custom matchers for domain concepts expect(response).toBeSuccessful(); expect(user).toHavePermission('admin'); // BAD: Vague assertions expect(result).toBeTruthy(); expect(data).toBeDefined(); expect(obj).toMatchObject({}); // Always passes! // BAD: Multiple unrelated assertions expect(user.name).toBe('John'); expect(user.email).toBe('john@example.com'); expect(user.createdAt).toBeDefined(); expect(user.updatedAt).toBeDefined(); expect(user.deletedAt).toBeNull(); // Better: Use snapshot or single toEqual ``` ### Async Testing ```typescript // GOOD: async/await it('should fetch user data', async () => { const data = await fetchUser('123'); expect(data.name).toBe('John'); }); // GOOD: Test rejections it('should reject for invalid user', async () => { await expect(fetchUser('invalid')).rejects.toThrow('User not found'); }); // GOOD: waitFor for async UI updates await waitFor(() => { expect(screen.getByText('Loaded')).toBeInTheDocument(); }); // BAD: done callback (error-prone) it('fetches data', (done) => { fetchUser('123').then((data) => { expect(data).toBeDefined(); done(); }); }); // BAD: No await (test passes incorrectly) it('should fail', () => { expect(asyncOperation()).rejects.toThrow(); // Missing await! }); ``` ### Test Organization ```typescript // GOOD: Descriptive nested describes describe('ShoppingCart', () => { describe('addItem', () => { it('should add new item to empty cart', () => {}); it('should increment quantity for existing item', () => {}); it('should throw for invalid item', () => {}); }); describe('removeItem', () => { it('should remove item from cart', () => {}); it('should throw for item not in cart', () => {}); }); }); // GOOD: Setup/teardown at appropriate levels describe('Database tests', () => { beforeAll(async () => { await db.connect(); }); afterAll(async () => { await db.disconnect(); }); beforeEach(async () => { await db.clear(); }); }); // BAD: Flat, unclear structure test('cart add', () => {}); test('cart add existing', () => {}); test('cart remove', () => {}); test('cart clear', () => {}); ``` ## Anti-Patterns to Avoid ### Flaky Tests ```typescript // BAD: Time-dependent it('should expire token', () => { const token = createToken(); // Depends on system time - will fail randomly expect(isExpired(token)).toBe(false); }); // GOOD: Control time it('should expire token after 1 hour', () => { jest.useFakeTimers(); const token = createToken(); jest.advanceTimersByTime(59 * 60 * 1000); expect(isExpired(token)).toBe(false); jest.advanceTimersByTime(2 * 60 * 1000); expect(isExpired(token)).toBe(true); }); // BAD: Random data without seed it('validates random email', () => { const email = `user${Math.random()}@test.com`; expect(isValid(email)).toBe(true); }); // GOOD: Deterministic test data it('validates various email formats', () => { const emails = ['user@test.com', 'user.name@test.co.uk']; emails.forEach(email => expect(isValid(email)).toBe(true)); }); ``` ### Test Coupling ```typescript // BAD: Tests depend on each other describe('User flow', () => { let userId: string; it('creates user', async () => { const user = await createUser(); userId = user.id; // Shared state! }); it('updates user', async () => { await updateUser(userId, {}); // Fails if first test fails! }); }); // GOOD: Independent tests describe('User operations', () => { it('creates user', async () => { const user = await createUser(); expect(user.id).toBeDefined(); }); it('updates existing user', async () => { const user = await createUser(); // Own setup const updated = await updateUser(user.id, { name: 'New' }); expect(updated.name).toBe('New'); }); }); ``` ### Testing Implementation Details ```typescript // BAD: Testing private methods/state it('should update internal counter', () => { const component = new Counter(); component.increment(); expect(component._internalCount).toBe(1); // Private! }); // GOOD: Test public behavior it('should display incremented value', () => { const component = new Counter(); component.increment(); expect(component.getValue()).toBe(1); }); // BAD: Testing method calls instead of effects it('should call logger', () => { const logger = { log: jest.fn() }; doSomething(logger); expect(logger.log).toHaveBeenCalled(); // Is this the real requirement? }); // GOOD: Test the actual effect it('should record action in audit log', async () => { await doSomething(); const logs = await getAuditLogs(); expect(logs).toContainEqual(expect.objectContaining({ action: 'something', timestamp: expect.any(Date) })); }); ``` ### Snapshot Abuse ```typescript // BAD: Large, unstable snapshots it('renders page', () => { expect(render()).toMatchSnapshot(); // 500+ line snapshot that changes constantly }); // GOOD: Small, focused snapshots it('renders error state correctly', () => { expect(render()).toMatchSnapshot(); }); // GOOD: Inline snapshots for small values it('formats date correctly', () => { expect(formatDate(date)).toMatchInlineSnapshot(`"Jan 13, 2026"`); }); ``` ## Configuration Best Practices ```javascript // jest.config.js module.exports = { // Clear mocks automatically clearMocks: true, // Coverage settings collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'], coverageThreshold: { global: { branches: 80, functions: 80, lines: 80 } }, // Fast test execution maxWorkers: '50%', // Fail fast in CI bail: process.env.CI ? 1 : 0, // Clear timeout testTimeout: 10000, }; ``` ## Quality Checklist | Check | Rule | |-------|------| | AAA structure | Arrange → Act → Assert clearly separated | | Isolation | Tests don't share state | | No flaky tests | No time/random dependencies | | Fast execution | Unit tests < 100ms each | | Clear assertions | Specific matchers, not toBeTruthy | | Minimal mocking | Only mock what's necessary | | Descriptive names | Test name describes behavior | | Cleanup | afterEach/afterAll for side effects |