.agent/skills/jest-expert/SKILL.md
Expert in Jest testing framework, advanced mocking strategies, snapshot testing, async patterns, TypeScript integration, and performance optimization
npx skillsauth add ripgraphics/authorsinfo jest-testing-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
I'm a specialized expert in the Jest testing framework with deep knowledge of configuration mastery, advanced mocking patterns, snapshot testing strategies, async testing patterns, custom matchers, and performance optimization.
jest.mock()jest.useFakeTimers() and jest.advanceTimersByTime()__mocks__ directoriesIssue: Cannot find module 'jest'
# Root Cause: Jest not installed or incorrect path
# Fix 1: Install Jest
npm install --save-dev jest
# Fix 2: Add to package.json devDependencies
{
"devDependencies": {
"jest": "^29.0.0"
}
}
# Diagnostic: npm list jest
# Validation: jest --version
Issue: Jest configuration not found
// ❌ Problematic: Missing configuration
// ✅ Solution: Create jest.config.js
module.exports = {
testEnvironment: 'node',
collectCoverageFrom: [
'src/**/*.{js,ts}',
'!src/**/*.d.ts'
],
testMatch: ['**/__tests__/**/*.(test|spec).(js|ts)']
};
Issue: SyntaxError: Cannot use import statement outside a module
// ❌ Problematic: ESM/CommonJS mismatch
// ✅ Solution 1: Add type: "module" to package.json
{
"type": "module",
"jest": {
"preset": "ts-jest/presets/default-esm",
"extensionsToTreatAsEsm": [".ts"]
}
}
// ✅ Solution 2: Configure babel-jest transformer
module.exports = {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
};
Issue: ReferenceError: window is not defined
// ❌ Problematic: Wrong test environment
// ✅ Solution: Set jsdom environment
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js']
};
// Or per-test environment
/**
* @jest-environment jsdom
*/
Issue: TypeError: regeneratorRuntime is not defined
// ❌ Problematic: Missing async/await polyfill
// ✅ Solution: Configure Babel preset
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
node: 'current'
}
}]
]
};
Issue: TypeScript files not being transformed
// ❌ Problematic: ts-jest not configured
// ✅ Solution: Configure TypeScript transformation
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
};
Issue: Cannot find module (TypeScript paths)
// ❌ Problematic: Path mapping not configured
// ✅ Solution: Add moduleNameMapping
module.exports = {
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@components/(.*)$': '<rootDir>/src/components/$1',
'^@utils/(.*)$': '<rootDir>/src/utils/$1'
}
};
Issue: Type errors in test files
// ❌ Problematic: Missing Jest types
// ✅ Solution: Install @types/jest
npm install --save-dev @types/jest
// Add to tsconfig.json
{
"compilerOptions": {
"types": ["jest", "node"]
}
}
// Use typed Jest functions
import { jest } from '@jest/globals';
const mockFn: jest.MockedFunction<typeof originalFunction> = jest.fn();
Issue: Mock implementation not called
// ❌ Problematic: Mock timing issue
beforeEach(() => {
mockFunction.mockClear(); // Wrong timing
});
// ✅ Solution: Proper mock setup
beforeEach(() => {
jest.clearAllMocks();
mockFunction.mockImplementation(() => 'mocked result');
});
// Verify mock calls
expect(mockFunction).toHaveBeenCalledWith(expectedArgs);
expect(mockFunction).toHaveBeenCalledTimes(1);
Issue: Module mock not working (hoisting problems)
// ❌ Problematic: Mock after import
import { userService } from './userService';
jest.mock('./userService'); // Too late - hoisting issue
// ✅ Solution: Mock at top of file
jest.mock('./userService', () => ({
__esModule: true,
default: {
getUser: jest.fn(),
updateUser: jest.fn(),
},
userService: {
getUser: jest.fn(),
updateUser: jest.fn(),
}
}));
Issue: Cannot redefine property (Object mocking)
// ❌ Problematic: Non-configurable property
Object.defineProperty(global, 'fetch', {
value: jest.fn(),
writable: false // This causes issues
});
// ✅ Solution: Proper property mocking
Object.defineProperty(global, 'fetch', {
value: jest.fn(),
writable: true,
configurable: true
});
// Or use spyOn for existing properties
const fetchSpy = jest.spyOn(global, 'fetch').mockImplementation();
Issue: Timer mocks not advancing
// ❌ Problematic: Fake timers not configured
test('delayed function', () => {
setTimeout(() => callback(), 1000);
// Timer never advances
});
// ✅ Solution: Proper timer mocking
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
test('delayed function', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
Issue: Async mock not resolving
// ❌ Problematic: Incorrect promise mock
const mockFn = jest.fn(() => Promise.resolve('result'));
// ✅ Solution: Use mockResolvedValue
const mockFn = jest.fn();
mockFn.mockResolvedValue('result');
// Or for rejections
mockFn.mockRejectedValue(new Error('Failed'));
// In tests
await expect(mockFn()).resolves.toBe('result');
await expect(mockFn()).rejects.toThrow('Failed');
Issue: Test timeout exceeded
// ❌ Problematic: Missing async handling
test('async operation', () => {
const result = asyncOperation(); // Returns promise
expect(result).toBe('expected'); // Fails - result is Promise
});
// ✅ Solution: Proper async patterns
test('async operation', async () => {
const result = await asyncOperation();
expect(result).toBe('expected');
}, 10000); // Custom timeout
// Or with resolves/rejects
test('async operation', () => {
return expect(asyncOperation()).resolves.toBe('expected');
});
Issue: Promise rejection unhandled
// ❌ Problematic: Missing error handling
test('error handling', async () => {
const result = await failingOperation(); // Unhandled rejection
});
// ✅ Solution: Proper error testing
test('error handling', async () => {
await expect(failingOperation()).rejects.toThrow('Expected error');
});
// Or with try/catch
test('error handling', async () => {
try {
await failingOperation();
fail('Should have thrown');
} catch (error) {
expect(error.message).toBe('Expected error');
}
});
Issue: Race condition in tests
// ❌ Problematic: Timing-dependent logic
test('race condition', () => {
triggerAsyncOperation();
expect(state).toBe('completed'); // Fails due to timing
});
// ✅ Solution: Use waitFor patterns
import { waitFor } from '@testing-library/react';
test('race condition', async () => {
triggerAsyncOperation();
await waitFor(() => {
expect(state).toBe('completed');
});
});
Issue: done() callback not called
// ❌ Problematic: Missing done() call
test('callback test', (done) => {
asyncCallback((error, result) => {
expect(result).toBe('success');
// Missing done() call causes timeout
});
});
// ✅ Solution: Always call done()
test('callback test', (done) => {
asyncCallback((error, result) => {
try {
expect(error).toBeNull();
expect(result).toBe('success');
done();
} catch (testError) {
done(testError);
}
});
});
Issue: Snapshot test failed
# ❌ Problematic: Blindly updating snapshots
jest --updateSnapshot
# ✅ Solution: Review changes carefully
jest --verbose --testNamePattern="snapshot test"
# Review diff in terminal
# Update only if changes are intentional
jest --updateSnapshot --testNamePattern="specific test"
Issue: Cannot write snapshot
// ❌ Problematic: Permission issues
// ✅ Solution: Check directory permissions
const fs = require('fs');
const path = require('path');
beforeAll(() => {
const snapshotDir = path.join(__dirname, '__snapshots__');
if (!fs.existsSync(snapshotDir)) {
fs.mkdirSync(snapshotDir, { recursive: true });
}
});
Issue: Snapshot serializer not working
// ❌ Problematic: Serializer not registered
// ✅ Solution: Add to setupFilesAfterEnv
// setupTests.js
expect.addSnapshotSerializer({
test: (val) => val && val.$$typeof === Symbol.for('react.element'),
print: (val, serialize) => serialize(val.props),
});
// Or in jest.config.js
module.exports = {
snapshotSerializers: ['enzyme-to-json/serializer'],
};
Issue: Snapshot too large
// ❌ Problematic: Full component snapshot
expect(wrapper).toMatchSnapshot();
// ✅ Solution: Targeted snapshots with property matchers
expect(wrapper.find('.important-section')).toMatchSnapshot();
// Or use property matchers
expect(user).toMatchSnapshot({
id: expect.any(String),
createdAt: expect.any(Date),
});
Issue: Tests running slowly
// ❌ Problematic: Sequential execution
module.exports = {
maxWorkers: 1, // Too conservative
};
// ✅ Solution: Optimize parallelization
module.exports = {
maxWorkers: '50%', // Use half of available cores
cache: true,
cacheDirectory: '<rootDir>/.jest-cache',
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
};
Issue: Out of memory error
// ❌ Problematic: Memory leaks
afterEach(() => {
// Missing cleanup
});
// ✅ Solution: Proper cleanup patterns
afterEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
// Clean up DOM if using jsdom
document.body.innerHTML = '';
});
// Run with memory monitoring
// jest --logHeapUsage --detectLeaks
Issue: Jest worker crashed
# ❌ Problematic: Too many workers
jest --maxWorkers=8 # On 4-core machine
# ✅ Solution: Adjust worker count
jest --maxWorkers=2
# Or increase Node.js memory
NODE_OPTIONS="--max-old-space-size=4096" jest
Issue: Coverage report empty
// ❌ Problematic: Wrong patterns
module.exports = {
collectCoverageFrom: [
'src/**/*.js', // Missing TypeScript files
],
};
// ✅ Solution: Comprehensive patterns
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,ts,jsx,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.*',
'!src/**/index.{js,ts}',
],
};
Issue: Coverage threshold not met
// ❌ Problematic: Unrealistic thresholds
module.exports = {
coverageThreshold: {
global: {
branches: 100, // Too strict
functions: 100,
lines: 100,
statements: 100
}
}
};
// ✅ Solution: Realistic thresholds
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
'./src/critical/': {
branches: 95,
functions: 95,
lines: 95,
statements: 95
}
}
};
Issue: Cannot debug Jest tests
# ❌ Problematic: Standard execution
jest
# ✅ Solution: Debug mode using Chrome DevTools
node --inspect-brk node_modules/.bin/jest --runInBand --no-cache
# Open chrome://inspect in Chrome browser to debug
# Alternative: Use console.log debugging
npm test -- --runInBand --verbose 2>&1 | tee test-debug.log
# Analyze test-debug.log for issues
Issue: Tests fail only in CI
# ❌ Problematic: Environment differences
# ✅ Solution: Consistent environments
CI=true NODE_ENV=test jest --ci --coverage --watchAll=false
# Ensure consistent Node.js version
node --version # Check version consistency
Issue: Jest cache issues in CI
# ❌ Problematic: Stale cache
# ✅ Solution: Clear cache in CI
jest --clearCache
jest --no-cache # For CI runs
Issue: Flaky tests in parallel execution
# ❌ Problematic: Race conditions
jest --maxWorkers=4
# ✅ Solution: Sequential execution for debugging
jest --runInBand --verbose
# Fix root cause, then re-enable parallelization
// jest.config.js - Production-ready configuration
module.exports = {
// Environment setup
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
// Module resolution
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'jest-transform-stub'
},
// Transform configuration
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
'^.+\\.(js|jsx)$': 'babel-jest'
},
// Test patterns
testMatch: [
'<rootDir>/src/**/__tests__/**/*.(ts|js)?(x)',
'<rootDir>/src/**/?(*.)(test|spec).(ts|js)?(x)'
],
// Coverage configuration
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/**/*.stories.{ts,tsx}',
'!src/**/__tests__/**',
'!src/**/__mocks__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
coverageReporters: ['text', 'lcov', 'html'],
// Performance optimization
maxWorkers: '50%',
cache: true,
cacheDirectory: '<rootDir>/.jest-cache',
// Global setup
globalSetup: '<rootDir>/tests/globalSetup.js',
globalTeardown: '<rootDir>/tests/globalTeardown.js',
// Watch mode optimization
watchPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/build/'],
// Snapshot configuration
snapshotSerializers: ['enzyme-to-json/serializer'],
// Test timeout
testTimeout: 10000,
};
// jest.config.js for TypeScript projects
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
globals: {
'ts-jest': {
tsconfig: {
compilerOptions: {
module: 'commonjs',
target: 'es2020',
lib: ['es2020', 'dom'],
skipLibCheck: true,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
moduleResolution: 'node',
resolveJsonModule: true,
isolatedModules: true,
noEmit: true
}
},
isolatedModules: true
}
},
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1'
}
};
// jest.config.js for ESM projects
module.exports = {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
useESM: true
}
},
moduleNameMapping: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
transform: {
'^.+\\.tsx?$': ['ts-jest', {
useESM: true
}]
}
};
// Level 1: Spy on existing methods
const apiSpy = jest.spyOn(api, 'fetchUser');
// Level 2: Stub with controlled responses
const mockFetch = jest.fn().mockResolvedValue({ data: mockUser });
// Level 3: Module-level mocking
jest.mock('./userService', () => ({
getUserById: jest.fn(),
updateUser: jest.fn(),
}));
// Level 4: Manual mocks for complex dependencies
// __mocks__/axios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
create: jest.fn(function () {
return this;
})
};
// Promise-based testing with better error messages
test('user creation with detailed assertions', async () => {
const userData = { name: 'John', email: '[email protected]' };
await expect(createUser(userData)).resolves.toMatchObject({
id: expect.any(String),
name: userData.name,
email: userData.email,
createdAt: expect.any(Date)
});
});
// Concurrent async testing
test('concurrent operations', async () => {
const promises = [
createUser({ name: 'User1' }),
createUser({ name: 'User2' }),
createUser({ name: 'User3' })
];
const results = await Promise.all(promises);
expect(results).toHaveLength(3);
expect(results.every(user => user.id)).toBe(true);
});
// setupTests.js - Custom matchers
expect.extend({
toBeValidEmail(received) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const pass = emailRegex.test(received);
return {
message: () => `expected ${received} ${pass ? 'not ' : ''}to be a valid email`,
pass
};
},
toHaveBeenCalledWithObjectMatching(received, expected) {
const calls = received.mock.calls;
const pass = calls.some(call =>
call.some(arg =>
typeof arg === 'object' &&
Object.keys(expected).every(key => arg[key] === expected[key])
)
);
return {
message: () => `expected mock to have been called with object matching ${JSON.stringify(expected)}`,
pass
};
}
});
// Performance benchmarking in tests
test('performance test', async () => {
const start = performance.now();
await performExpensiveOperation();
const end = performance.now();
const duration = end - start;
expect(duration).toBeLessThan(1000); // Should complete in under 1 second
});
// Memory usage testing
test('memory usage test', () => {
const initialMemory = process.memoryUsage().heapUsed;
// Perform operations that should not leak memory
for (let i = 0; i < 1000; i++) {
createAndDestroyObject();
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
const finalMemory = process.memoryUsage().heapUsed;
const memoryGrowth = finalMemory - initialMemory;
expect(memoryGrowth).toBeLessThan(1024 * 1024); // Less than 1MB growth
});
# Jest version and environment
jest --version
node --version
npm list jest ts-jest @types/jest
# Configuration validation
jest --showConfig
jest --listTests
# Memory and performance monitoring
jest --logHeapUsage --detectLeaks --verbose
# Cache management
jest --clearCache
jest --no-cache --runInBand
# Worker optimization
jest --maxWorkers=1 --runInBand
jest --maxWorkers=50%
# Debug specific tests
jest --testNamePattern="failing test" --verbose --no-cache
jest --testPathPattern="src/components" --verbose
# Debug with Node.js debugger
node --inspect-brk node_modules/.bin/jest --runInBand --no-cache
# Watch mode debugging
jest --watch --verbose --no-coverage
# Coverage generation
jest --coverage --coverageReporters=text --coverageReporters=html
jest --coverage --collectCoverageFrom="src/critical/**/*.{js,ts}"
# Coverage threshold testing
jest --coverage --passWithNoTests
I specialize in making Jest work optimally for your specific use case, ensuring fast, reliable tests with comprehensive coverage and maintainable configuration. Let me help you master Jest's advanced features and resolve complex testing challenges.
When reviewing Jest test code, focus on:
tools
Webpack build optimization expert with deep knowledge of configuration patterns, bundle analysis, code splitting, module federation, performance optimization, and plugin/loader ecosystem. Use PROACTIVELY for any Webpack bundling issues including complex optimizations, build performance, custom plugins/loaders, and modern architecture patterns. If a specialized expert is a better fit, I will recommend switching and stop.
development
Web application security expert. OWASP Top 10, XSS, SQLi, CSRF, SSRF, authentication bypass, IDOR. Use for web app security testing.
testing
Vitest testing framework expert for Vite integration, Jest migration, browser mode testing, and performance optimization
tools
Vite build optimization expert with deep knowledge of ESM-first development, HMR optimization, plugin ecosystem, production builds, library mode, and SSR configuration. Use PROACTIVELY for any Vite bundling issues including dev server performance, build optimization, plugin development, and modern ESM patterns. If a specialized expert is a better fit, I will recommend switching and stop.