Claude-skill-registry jest-patterns
Jest testing patterns, anti-patterns, and quality rules
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/jest-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-jest-patterns && rm -rf "$T"
manifest:
skills/data/jest-patterns/SKILL.mdsource content
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)
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// BAD: Large, unstable snapshots it('renders page', () => { expect(render(<FullPage />)).toMatchSnapshot(); // 500+ line snapshot that changes constantly }); // GOOD: Small, focused snapshots it('renders error state correctly', () => { expect(render(<ErrorMessage error="Not found" />)).toMatchSnapshot(); }); // GOOD: Inline snapshots for small values it('formats date correctly', () => { expect(formatDate(date)).toMatchInlineSnapshot(`"Jan 13, 2026"`); });
Configuration Best Practices
// 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 |