| name | jest-generator |
| description | Generate Jest-based unit tests for JavaScript/TypeScript code. Creates test files following Jest conventions with proper mocking, describe blocks, and coverage. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
Jest Generator Skill
Purpose
This skill generates Jest-based unit tests for JavaScript and TypeScript code, following Jest conventions, best practices, and project standards. It creates comprehensive test suites with proper mocking, describe blocks, and code organization.
When to Use
- Generate Jest tests for JavaScript/TypeScript modules
- Create test files for React components
- Add missing test coverage to existing JS/TS code
- Need Jest-specific patterns (mocks, spies, snapshots)
Test File Naming Convention
Source to Test Mapping:
- Source:
src/components/Feature.tsx
- Test:
src/components/Feature.test.tsx
- Pattern:
<source_filename>.test.ts or <source_filename>.test.js
Examples:
src/utils/validator.ts → src/utils/validator.test.ts
src/models/User.ts → src/models/User.test.ts
src/services/api.js → src/services/api.test.js
src/components/Button.tsx → src/components/Button.test.tsx
Jest Test Generation Workflow
1. Analyze JavaScript/TypeScript Source Code
Read the source file:
cat src/components/Feature.tsx
Identify test targets:
- Exported functions to test
- Classes and methods
- React components (if applicable)
- Error conditions
- Edge cases
- Dependencies and imports
Output: List of functions/classes/components requiring tests
2. Generate Test File Structure
Create test file with proper naming:
import { functionToTest, ClassToTest, ComponentToTest } from './Feature';
import { mockDependency } from './__mocks__/dependency';
jest.mock('./dependency');
jest.mock('external-library');
describe('ModuleName', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('ClassName', () => {
let instance: ClassToTest;
beforeEach(() => {
instance = new ClassToTest();
});
describe('constructor', () => {
it('should initialize with valid parameters', () => {
const instance = new ClassToTest({ param: 'value' });
expect(instance.param).toBe('value');
expect(instance.initialized).toBe(true);
});
it('should throw error with invalid parameters', () => {
const invalidParams = null;
expect(() => new ClassToTest(invalidParams)).toThrow('Invalid parameters');
});
});
describe('method', () => {
it('should return expected result with valid input', () => {
const input = { name: 'test', value: 123 };
const result = instance.method(input);
expect(result.processed).toBe(true);
expect(result.name).toBe('test');
expect(result.value).toBe(123);
});
it('should throw error with invalid input', () => {
const invalidInput = null;
expect(() => instance.method(invalidInput)).toThrow('Invalid input');
});
it('should handle edge case with empty input', () => {
const emptyInput = {};
const result = instance.method(emptyInput);
expect(result).toEqual({});
});
});
});
describe('functionToTest', () => {
it('should return expected result with valid input', () => {
const input = { key: 'value' };
const expected = { processed: true, key: 'value' };
const result = functionToTest(input);
expect(result).toEqual(expected);
});
it('should handle null input', () => {
const input = null;
expect(() => functionToTest(input)).toThrow('Input cannot be null');
});
it('should handle undefined input', () => {
const input = undefined;
const result = functionToTest(input);
expect(result).toBeUndefined();
});
});
describe('functionWithDependency', () => {
it('should call dependency with correct parameters', () => {
const input = { key: 'value' };
const mockDep = jest.fn().mockReturnValue({ status: 'success' });
const result = functionWithDependency(input, mockDep);
expect(mockDep).toHaveBeenCalledWith(input);
expect(mockDep).toHaveBeenCalledTimes(1);
expect(result.status).toBe('success');
});
it('should handle dependency error', () => {
const input = { key: 'value' };
const mockDep = jest.fn().mockRejectedValue(new Error('API error'));
await expect(functionWithDependency(input, mockDep))
.rejects.toThrow('API error');
});
});
describe('asyncFunction', () => {
it('should resolve with expected result', async () => {
const input = { key: 'value' };
const result = await asyncFunction(input);
expect(result.success).toBe(true);
expect(result.data).toEqual(input);
});
it('should reject with error on failure', async () => {
const invalidInput = null;
await expect(asyncFunction(invalidInput))
.rejects.toThrow('Invalid input');
});
it('should handle timeout', async () => {
jest.useFakeTimers();
const promise = asyncFunctionWithTimeout();
jest.advanceTimersByTime(5000);
await expect(promise).rejects.toThrow('Timeout');
jest.useRealTimers();
});
});
describe('validation', () => {
it.each([
['valid@email.com', true],
['invalid.email', false],
['', false],
[null, false],
['@no-user.com', false],
])('should validate email "%s" as %s', (email, expected) => {
const result = validateEmail(email);
expect(result).toBe(expected);
});
});
describe('permissions', () => {
it.each([
['admin', 'all'],
['moderator', 'edit'],
['user', 'read'],
['guest', 'none'],
])('should return "%s" permission for %s user', (userType, expected) => {
const user = { type: userType };
const result = getPermissions(user);
expect(result).toBe(expected);
});
});
});
Deliverable: Complete Jest test file
Jest-Specific Patterns
1. Mocking
Mock entire module:
jest.mock('./api', () => ({
fetchData: jest.fn(),
postData: jest.fn(),
}));
Mock specific function:
import { fetchData } from './api';
jest.mock('./api');
describe('Component', () => {
it('should fetch data', async () => {
(fetchData as jest.Mock).mockResolvedValue({ data: 'test' });
const result = await getData();
expect(result.data).toBe('test');
});
});
Mock with implementation:
const mockFn = jest.fn((x: number) => x * 2);
expect(mockFn(2)).toBe(4);
expect(mockFn).toHaveBeenCalledWith(2);
Spy on method:
const obj = {
method: () => 'original',
};
const spy = jest.spyOn(obj, 'method').mockReturnValue('mocked');
expect(obj.method()).toBe('mocked');
expect(spy).toHaveBeenCalled();
spy.mockRestore();
2. Async Testing
Testing promises:
it('should resolve with data', async () => {
const result = await asyncFunction();
expect(result).toBeDefined();
});
it('should reject with error', async () => {
await expect(asyncFunction()).rejects.toThrow('Error');
});
it('should handle promise chain', () => {
return asyncFunction().then(result => {
expect(result).toBeDefined();
});
});
Testing async with done callback:
it('should complete async operation', (done) => {
asyncOperation((result) => {
expect(result).toBe('complete');
done();
});
});
3. Timers
Mock timers:
describe('with fake timers', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('should execute after timeout', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
it('should run all timers', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
setTimeout(callback, 2000);
jest.runAllTimers();
expect(callback).toHaveBeenCalledTimes(2);
});
});
4. Snapshots
Snapshot testing:
it('should match snapshot', () => {
const data = {
name: 'test',
value: 123,
nested: {
field: 'value',
},
};
expect(data).toMatchSnapshot();
});
it('should match inline snapshot', () => {
const result = formatData({ name: 'test' });
expect(result).toMatchInlineSnapshot(`
{
"formatted": true,
"name": "test",
}
`);
});
5. Setup and Teardown
Test lifecycle hooks:
describe('TestSuite', () => {
beforeAll(() => {
});
afterAll(() => {
});
beforeEach(() => {
});
afterEach(() => {
});
it('test 1', () => {});
it('test 2', () => {});
});
6. Matchers
Common Jest matchers:
expect(value).toBe(expected);
expect(value).toEqual(expected);
expect(value).toStrictEqual(expected);
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(5);
expect(value).toBeCloseTo(0.3, 5);
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
expect(array).toContain(item);
expect(array).toHaveLength(3);
expect(array).toContainEqual(item);
expect(obj).toHaveProperty('key');
expect(obj).toHaveProperty('key', value);
expect(obj).toMatchObject({ key: value });
expect(fn).toThrow();
expect(fn).toThrow('error message');
expect(fn).toThrow(ErrorClass);
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(arg1, arg2);
expect(mockFn).toHaveBeenLastCalledWith(arg);
React Component Testing
Basic Component Test
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Button } from './Button';
describe('Button', () => {
it('should render with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('should call onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should be disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByText('Click me')).toBeDisabled();
});
});
Component with Hooks
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('should initialize with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('should increment count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
Running Jest
Basic commands:
npm test
jest
jest src/components/Feature.test.ts
jest Feature
jest --coverage
jest --watch
jest --verbose
jest --updateSnapshot
jest -u
Coverage commands:
jest --coverage
open coverage/lcov-report/index.html
jest --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
Best Practices
- Use descriptive test names:
should <expected> when <condition>
- Organize with describe blocks: Group related tests
- Use beforeEach/afterEach: Clean setup and teardown
- Mock external dependencies: Isolate unit under test
- Test user behavior: For components, test what users see/do
- Use test.each for similar tests: Reduce duplication
- Keep tests independent: No test depends on another
- Test edge cases: Null, undefined, empty values
- Test error conditions: Exceptions and rejections
- Use appropriate matchers: Choose the most specific matcher
TypeScript-Specific Considerations
Type-safe mocks:
import { MockedFunction } from 'jest-mock';
const mockFn: MockedFunction<typeof realFunction> = jest.fn();
Type assertions:
const mock = jest.fn() as jest.MockedFunction<typeof original>;
Generic mocks:
interface ApiResponse<T> {
data: T;
status: number;
}
const mockApi = jest.fn<Promise<ApiResponse<User>>, [string]>();
Quality Checklist
Before marking tests complete:
Integration with Testing Workflow
Input: JavaScript/TypeScript source file to test
Process: Analyze → Generate structure → Write tests → Run & verify
Output: Jest test file with ≥ 80% coverage
Next Step: Integration testing or code review
Remember
- Follow naming convention:
<source_file>.test.ts
- Use describe blocks for organization
- Mock external dependencies to isolate tests
- Test behavior, not implementation
- Use appropriate matchers for clarity
- Aim for 80%+ coverage
- Keep tests fast and independent
- For React: test what users see and do