jest-testing

Jest Testing Expertise

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "jest-testing" with this command: npx skills add c0ntr0lledcha0s/claude-code-plugin-automations/c0ntr0lledcha0s-claude-code-plugin-automations-jest-testing

Jest Testing Expertise

You are an expert in Jest testing framework with deep knowledge of its configuration, matchers, mocks, and best practices for testing JavaScript and TypeScript applications.

Your Capabilities

  • Jest Configuration: Setup, configuration files, environments, and presets

  • Matchers & Assertions: Built-in and custom matchers, asymmetric matchers

  • Mocking: Mock functions, modules, timers, and external dependencies

  • Snapshot Testing: Inline and external snapshots, snapshot updates

  • Code Coverage: Coverage configuration, thresholds, and reports

  • Test Organization: Describe blocks, hooks, test filtering

  • React Testing: Testing React components with Jest DOM and RTL

When to Use This Skill

Claude should automatically invoke this skill when:

  • The user mentions Jest, jest.config, or Jest-specific features

  • Files matching *.test.js , *.test.ts , *.test.jsx , *.test.tsx are encountered

  • The user asks about mocking, snapshots, or Jest matchers

  • The conversation involves testing React, Node.js, or JavaScript apps

  • Jest configuration or setup is discussed

How to Use This Skill

Accessing Resources

Use {baseDir} to reference files in this skill directory:

  • Scripts: {baseDir}/scripts/

  • Documentation: {baseDir}/references/

  • Templates: {baseDir}/assets/

Progressive Discovery

  • Start with core Jest expertise

  • Reference specific documentation as needed

  • Provide code examples from templates

Available Resources

This skill includes ready-to-use resources in {baseDir} :

  • references/jest-cheatsheet.md - Quick reference for matchers, mocks, async patterns, and CLI commands

  • assets/test-file.template.ts - Complete test templates for unit tests, async tests, class tests, mock tests, React components, and hooks

  • scripts/check-jest-setup.sh - Validates Jest configuration and dependencies

Jest Best Practices

Test Structure

describe('ComponentName', () => { beforeEach(() => { // Setup });

afterEach(() => { // Cleanup });

describe('method or behavior', () => { it('should do expected thing when condition', () => { // Arrange // Act // Assert }); }); });

Mocking Patterns

Mock Functions

const mockFn = jest.fn(); mockFn.mockReturnValue('value'); mockFn.mockResolvedValue('async value'); mockFn.mockImplementation((arg) => arg * 2);

Mock Modules

jest.mock('./module', () => ({ func: jest.fn().mockReturnValue('mocked'), }));

Mock Timers

jest.useFakeTimers(); jest.advanceTimersByTime(1000); jest.runAllTimers();

Common Matchers

expect(value).toBe(expected); // Strict equality expect(value).toEqual(expected); // Deep equality expect(value).toBeTruthy(); // Truthy expect(value).toContain(item); // Array/string contains expect(fn).toHaveBeenCalledWith(args); // Function called with expect(value).toMatchSnapshot(); // Snapshot expect(fn).toThrow(error); // Throws

Async Testing

// Promises it('async test', async () => { await expect(asyncFn()).resolves.toBe('value'); });

// Callbacks it('callback test', (done) => { callbackFn((result) => { expect(result).toBe('value'); done(); }); });

Jest Configuration

Basic Configuration

// jest.config.js module.exports = { testEnvironment: 'node', // or 'jsdom' roots: ['<rootDir>/src'], testMatch: ['/tests//.ts', '**/.test.ts'], transform: { '^.+\.tsx?$': 'ts-jest', }, moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', }, coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, };

React Testing Library

Setup with Custom Render

// test-utils.tsx import { render, RenderOptions } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { BrowserRouter } from 'react-router-dom';

const AllProviders = ({ children }: { children: React.ReactNode }) => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, });

return ( <QueryClientProvider client={queryClient}> <BrowserRouter> {children} </BrowserRouter> </QueryClientProvider> ); };

export const renderWithProviders = ( ui: React.ReactElement, options?: RenderOptions ) => render(ui, { wrapper: AllProviders, ...options });

export * from '@testing-library/react';

Query Priority (Best to Worst)

// 1. Accessible queries (best) screen.getByRole('button', { name: 'Submit' }); screen.getByLabelText('Email'); screen.getByPlaceholderText('Enter email'); screen.getByText('Welcome');

// 2. Semantic queries screen.getByAltText('Profile picture'); screen.getByTitle('Close');

// 3. Test IDs (last resort) screen.getByTestId('submit-button');

User Interactions

import userEvent from '@testing-library/user-event';

test('form submission', async () => { const user = userEvent.setup(); render(<LoginForm />);

// Type in inputs await user.type(screen.getByLabelText('Email'), 'test@example.com'); await user.type(screen.getByLabelText('Password'), 'password123');

// Click button await user.click(screen.getByRole('button', { name: 'Sign in' }));

// Check result await waitFor(() => { expect(screen.getByText('Welcome!')).toBeInTheDocument(); }); });

test('keyboard navigation', async () => { const user = userEvent.setup(); render(<Form />);

await user.tab(); // Focus first element await user.keyboard('{Enter}'); // Press enter await user.keyboard('[ShiftLeft>][Tab][/ShiftLeft]'); // Shift+Tab });

Testing Hooks

import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter';

test('useCounter increments', () => { const { result } = renderHook(() => useCounter());

expect(result.current.count).toBe(0);

act(() => { result.current.increment(); });

expect(result.current.count).toBe(1); });

// With wrapper for context test('hook with context', () => { const wrapper = ({ children }) => ( <ThemeProvider theme="dark">{children}</ThemeProvider> );

const { result } = renderHook(() => useTheme(), { wrapper }); expect(result.current.theme).toBe('dark'); });

Async Assertions

import { waitFor, waitForElementToBeRemoved } from '@testing-library/react';

test('async loading', async () => { render(<DataFetcher />);

// Wait for loading to disappear await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));

// Wait for content await waitFor(() => { expect(screen.getByText('Data loaded')).toBeInTheDocument(); });

// With timeout await waitFor( () => expect(screen.getByText('Slow content')).toBeInTheDocument(), { timeout: 5000 } ); });

Network Mocking with MSW

Setup

// src/mocks/handlers.ts import { http, HttpResponse } from 'msw';

export const handlers = [ http.get('/api/users', () => { return HttpResponse.json([ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ]); }),

http.post('/api/users', async ({ request }) => { const body = await request.json(); return HttpResponse.json({ id: 3, ...body }, { status: 201 }); }),

http.delete('/api/users/:id', ({ params }) => { return HttpResponse.json({ deleted: params.id }); }), ];

// src/mocks/server.ts import { setupServer } from 'msw/node'; import { handlers } from './handlers';

export const server = setupServer(...handlers);

Jest Setup

// jest.setup.ts import { server } from './src/mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

Test-Specific Handlers

import { server } from '../mocks/server'; import { http, HttpResponse } from 'msw';

test('handles error response', async () => { // Override for this test only server.use( http.get('/api/users', () => { return HttpResponse.json( { error: 'Server error' }, { status: 500 } ); }) );

render(<UserList />);

await waitFor(() => { expect(screen.getByText('Failed to load users')).toBeInTheDocument(); }); });

test('handles network error', async () => { server.use( http.get('/api/users', () => { return HttpResponse.error(); }) );

render(<UserList />);

await waitFor(() => { expect(screen.getByText('Network error')).toBeInTheDocument(); }); });

Request Assertions

test('sends correct request', async () => { let capturedRequest: Request | null = null;

server.use( http.post('/api/users', async ({ request }) => { capturedRequest = request.clone(); return HttpResponse.json({ id: 1 }); }) );

render(<CreateUserForm />);

await userEvent.type(screen.getByLabelText('Name'), 'John'); await userEvent.click(screen.getByRole('button', { name: 'Create' }));

await waitFor(() => { expect(capturedRequest).not.toBeNull(); });

const body = await capturedRequest!.json(); expect(body).toEqual({ name: 'John' }); });

Custom Matchers

Creating Custom Matchers

// jest.setup.ts expect.extend({ toBeWithinRange(received: number, floor: number, ceiling: number) { const pass = received >= floor && received <= ceiling; return { pass, message: () => pass ? expected ${received} not to be within range ${floor} - ${ceiling} : expected ${received} to be within range ${floor} - ${ceiling}, }; },

toHaveBeenCalledOnceWith(received: jest.Mock, ...args: unknown[]) { const pass = received.mock.calls.length === 1 && JSON.stringify(received.mock.calls[0]) === JSON.stringify(args); return { pass, message: () => pass ? expected not to be called once with ${args} : expected to be called once with ${args}, but was called ${received.mock.calls.length} times, }; }, });

// Type declarations declare global { namespace jest { interface Matchers<R> { toBeWithinRange(floor: number, ceiling: number): R; toHaveBeenCalledOnceWith(...args: unknown[]): R; } } }

Asymmetric Matchers

test('asymmetric matchers', () => { const data = { id: 123, name: 'Test', createdAt: new Date().toISOString(), };

expect(data).toEqual({ id: expect.any(Number), name: expect.stringContaining('Test'), createdAt: expect.stringMatching(/^\d{4}-\d{2}-\d{2}/), });

expect(['a', 'b', 'c']).toEqual( expect.arrayContaining(['a', 'c']) );

expect({ a: 1, b: 2, c: 3 }).toEqual( expect.objectContaining({ a: 1, b: 2 }) ); });

Debugging Jest Tests

Debug Output

import { screen } from '@testing-library/react';

test('debugging', () => { render(<MyComponent />);

// Print DOM screen.debug();

// Print specific element screen.debug(screen.getByRole('button'));

// Get readable DOM console.log(prettyDOM(container)); });

Finding Slow Tests

Run with verbose timing

jest --verbose

Detect open handles

jest --detectOpenHandles

Run tests serially to find interactions

jest --runInBand

Common Debug Patterns

// Check what's in the DOM test('debug queries', () => { render(<MyComponent />);

// Log all available roles screen.getByRole(''); // Will error with available roles

// Check accessible name screen.logTestingPlaygroundURL(); // Opens playground });

// Debug async issues test('async debug', async () => { render(<AsyncComponent />);

// Use findBy for async elements const element = await screen.findByText('Loaded');

// Log state at each step screen.debug(); });

CI/CD Integration

GitHub Actions Workflow

.github/workflows/test.yml

name: Tests

on: [push, pull_request]

jobs: test: runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'

  - name: Install dependencies
    run: npm ci

  - name: Run tests
    run: npm test -- --coverage --ci

  - name: Upload coverage
    uses: codecov/codecov-action@v3
    with:
      files: ./coverage/lcov.info

Jest CI Configuration

// jest.config.js module.exports = { // ... other config

// CI-specific settings ...(process.env.CI && { maxWorkers: 2, ci: true, coverageReporters: ['lcov', 'text-summary'], }),

// Coverage thresholds coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, };

Caching Dependencies

In GitHub Actions

  • name: Cache Jest uses: actions/cache@v3 with: path: | node_modules/.cache/jest key: jest-${{ runner.os }}-${{ hashFiles('**/jest.config.js') }}

Common Issues & Solutions

Issue: Tests are slow

  • Use jest.mock() for expensive modules

  • Run tests in parallel with --maxWorkers

  • Use beforeAll for expensive setup

  • Mock network requests with MSW

Issue: Flaky tests

  • Mock timers for timing-dependent code

  • Use waitFor for async state changes

  • Avoid shared mutable state

  • Use findBy queries for async elements

Issue: Mock not working

  • Ensure mock is before import

  • Use jest.resetModules() between tests

  • Check module path matches exactly

  • Use jest.doMock() for dynamic mocks

Issue: Memory leaks

  • Clean up in afterEach

  • Mock timers with jest.useFakeTimers()

  • Use --detectLeaks flag

  • Check for unresolved promises

Examples

Example 1: Testing a React Component

When testing React components:

  • Check for React Testing Library usage

  • Verify proper queries (getByRole, getByLabelText)

  • Test user interactions with userEvent

  • Assert on accessible elements

Example 2: Testing API Calls

When testing code that makes API calls:

  • Mock fetch or axios at module level

  • Test success and error scenarios

  • Verify request parameters

  • Test loading states

Version Compatibility

The patterns in this skill require the following minimum versions:

Package Minimum Version Features Used

Jest 29.0+ Modern mock APIs, ESM support

@testing-library/react 14.0+ renderHook in main package

@testing-library/user-event 14.0+ userEvent.setup() API

msw 2.0+ http, HttpResponse (v1 used rest, ctx)

@testing-library/jest-dom 6.0+ Modern matchers

Migration Notes

MSW v1 → v2:

// v1 (deprecated) import { rest } from 'msw'; rest.get('/api', (req, res, ctx) => res(ctx.json(data)));

// v2 (current) import { http, HttpResponse } from 'msw'; http.get('/api', () => HttpResponse.json(data));

user-event v13 → v14:

// v13 (deprecated) userEvent.click(button);

// v14 (current) const user = userEvent.setup(); await user.click(button);

Important Notes

  • Jest is automatically invoked by Claude when relevant

  • Always check for jest.config.js/ts for project-specific settings

  • Use {baseDir} variable to reference skill resources

  • Prefer Testing Library queries over direct DOM access for React

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

writing-docs

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

investigating-codebases

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

building-mcp-servers

No summary provided by upstream source.

Repository SourceNeeds Review