Tests JavaScript and TypeScript applications with Jest test runner including mocking, snapshot testing, and code coverage. Use when setting up testing, writing unit tests, or when user mentions Jest, test runner, or JavaScript testing.
Runs Jest tests for JavaScript/TypeScript apps with mocking, snapshots, and coverage. Activates when you mention Jest, testing setup, or writing unit tests.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Delightful JavaScript testing framework with zero configuration and rich mocking capabilities.
# Install
npm install -D jest @types/jest
# With TypeScript
npm install -D jest ts-jest @types/jest
npx ts-jest config:init
# Or with Babel
npm install -D jest @babel/preset-env @babel/preset-typescript
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/*.test.ts', '**/*.spec.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/__tests__/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
clearMocks: true,
verbose: true,
};
export default config;
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js',
},
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.jest.json' }],
},
};
export default config;
import '@testing-library/jest-dom';
// Global mocks
global.fetch = jest.fn();
// Extend expect
expect.extend({
toBeWithinRange(received: number, floor: number, ceiling: number) {
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () =>
`expected ${received} ${pass ? 'not ' : ''}to be within range ${floor} - ${ceiling}`,
};
},
});
declare global {
namespace jest {
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
}
import { describe, it, expect, test, beforeEach, afterEach } from '@jest/globals';
describe('Calculator', () => {
let calculator: Calculator;
beforeEach(() => {
calculator = new Calculator();
});
afterEach(() => {
// Cleanup
});
describe('add', () => {
it('should add two positive numbers', () => {
expect(calculator.add(1, 2)).toBe(3);
});
it('should handle negative numbers', () => {
expect(calculator.add(-1, -2)).toBe(-3);
});
test.each([
[1, 2, 3],
[0, 0, 0],
[-1, 1, 0],
[100, 200, 300],
])('add(%i, %i) should return %i', (a, b, expected) => {
expect(calculator.add(a, b)).toBe(expected);
});
});
describe('divide', () => {
it('should throw when dividing by zero', () => {
expect(() => calculator.divide(10, 0)).toThrow('Division by zero');
});
});
});
// Equality
expect(value).toBe(expected); // ===
expect(value).toEqual(expected); // Deep equality
expect(value).toStrictEqual(expected); // Deep + undefined props
// Truthiness
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
expect(value).toBeTruthy();
expect(value).toBeFalsy();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3);
expect(value).toBeLessThan(5);
expect(value).toBeCloseTo(0.3, 5); // Floating point
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
expect(string).toHaveLength(5);
// Arrays
expect(array).toContain(item);
expect(array).toContainEqual(object);
expect(array).toHaveLength(3);
// Objects
expect(object).toHaveProperty('key');
expect(object).toHaveProperty('nested.key', value);
expect(object).toMatchObject({ key: value });
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow('message');
expect(() => fn()).toThrow(ErrorType);
// Negation
expect(value).not.toBe(other);
// Asymmetric matchers
expect(value).toEqual(expect.any(Number));
expect(value).toEqual(expect.stringContaining('sub'));
expect(value).toEqual(expect.arrayContaining([1, 2]));
expect(value).toEqual(expect.objectContaining({ key: value }));
// Return promise
test('fetches user data', () => {
return fetchUser(1).then((user) => {
expect(user.name).toBe('Alice');
});
});
// Async/await
test('fetches user data', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Alice');
});
// Resolves/rejects
test('resolves to user', async () => {
await expect(fetchUser(1)).resolves.toEqual({ name: 'Alice' });
});
test('rejects with error', async () => {
await expect(fetchUser(-1)).rejects.toThrow('User not found');
});
test('callback with done', (done) => {
fetchDataWithCallback((error, data) => {
try {
expect(error).toBeNull();
expect(data).toEqual({ success: true });
done();
} catch (e) {
done(e);
}
});
});
jest.useFakeTimers();
test('delays execution', () => {
const callback = jest.fn();
delayedCall(callback, 1000);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
});
test('with modern timers', () => {
jest.useFakeTimers({ advanceTimers: true });
const callback = jest.fn();
setTimeout(callback, 1000);
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
});
afterEach(() => {
jest.useRealTimers();
});
// Create mock function
const mockFn = jest.fn();
// With implementation
const mockAdd = jest.fn((a, b) => a + b);
// Mock return values
mockFn.mockReturnValue(42);
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2);
mockFn.mockResolvedValue({ data: 'value' });
mockFn.mockRejectedValue(new Error('Failed'));
// Assertions
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenLastCalledWith('lastArg');
expect(mockFn).toHaveBeenNthCalledWith(1, 'firstCallArg');
expect(mockFn).toHaveReturnedWith(42);
// __mocks__/axios.ts
const axios = {
get: jest.fn(),
post: jest.fn(),
create: jest.fn(() => axios),
};
export default axios;
// In test file
jest.mock('axios');
import axios from 'axios';
const mockedAxios = axios as jest.Mocked<typeof axios>;
test('fetches data', async () => {
mockedAxios.get.mockResolvedValue({ data: { users: [] } });
const result = await fetchUsers();
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users');
expect(result).toEqual({ users: [] });
});
// Mock specific exports
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
expensiveOperation: jest.fn(),
}));
// Mock with factory
jest.mock('./database', () => {
return {
connect: jest.fn().mockResolvedValue(true),
query: jest.fn(),
};
});
const video = {
play() {
return true;
},
};
test('plays video', () => {
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
// Spy on prototype
jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2024-01-01');
// __mocks__/fs.ts
const fs = jest.createMockFromModule('fs') as typeof import('fs');
let mockFiles: Record<string, string> = {};
fs.readFileSync = jest.fn((path: string) => {
if (path in mockFiles) {
return mockFiles[path];
}
throw new Error(`ENOENT: ${path}`);
});
(fs as any).__setMockFiles = (files: Record<string, string>) => {
mockFiles = files;
};
export default fs;
// Basic snapshot
test('renders correctly', () => {
const tree = renderer.create(<Button label="Click me" />).toJSON();
expect(tree).toMatchSnapshot();
});
// Inline snapshot
test('formats date', () => {
expect(formatDate(new Date('2024-01-15'))).toMatchInlineSnapshot(
`"January 15, 2024"`
);
});
// Property matchers for dynamic values
test('creates user', () => {
const user = createUser('Alice');
expect(user).toMatchSnapshot({
id: expect.any(String),
createdAt: expect.any(Date),
});
});
// Update snapshots: jest --updateSnapshot
# Run with coverage
jest --coverage
# Coverage report options
jest --coverage --coverageReporters="text" --coverageReporters="html"
// jest.config.ts coverage options
{
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
'./src/utils/': {
branches: 100,
statements: 100,
},
},
}
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('submits form with user data', async () => {
const handleSubmit = jest.fn();
const user = userEvent.setup();
render(<LoginForm onSubmit={handleSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(handleSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
test('shows error on invalid input', async () => {
render(<LoginForm />);
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
});
test('fetches and displays users', async () => {
const mockUsers = [{ id: 1, name: 'Alice' }];
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockUsers),
});
render(<UserList />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
# Run all tests
jest
# Watch mode
jest --watch
# Run specific file
jest path/to/test.spec.ts
# Run tests matching pattern
jest --testNamePattern="should add"
# Run only changed files
jest --onlyChanged
# Parallel execution
jest --maxWorkers=4
# Debug mode
node --inspect-brk node_modules/.bin/jest --runInBand
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.