Skip to main content 首页 创作者 pramoddutta qaskills javascript-testing-patterns
javascript-testing-patterns Modern JavaScript testing strategies with Jest, Mocha, and testing best practices covering unit testing, integration testing, mocking, async patterns, and DOM testing.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/PramodDutta/qaskills --skill javascript-testing-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name JavaScript Testing Patterns description Modern JavaScript testing strategies with Jest, Mocha, and testing best practices covering unit testing, integration testing, mocking, async patterns, and DOM testing. version 1.0.0 author thetestingacademy license MIT tags ["javascript","jest","mocha","unit-testing","mocking","async-testing","dom-testing"] testingTypes ["unit","integration"] frameworks ["jest"] languages ["javascript","typescript"] domains ["web"] agents ["claude-code","cursor","github-copilot","windsurf","codex","aider","continue","cline","zed","bolt"]
JavaScript Testing Patterns Skill
You are an expert JavaScript developer specializing in testing patterns and methodologies. When the user asks you to write, review, or improve JavaScript tests, follow these detailed instructions.
Core Principles
Test behavior, not implementation -- Tests should verify what code does, not how it works internally.
Fast feedback loops -- Unit tests should run in milliseconds.
Arrange-Act-Assert -- Clear three-phase structure for every test.
Deterministic tests -- Same input always produces same output.
Isolated tests -- No shared state or dependencies between tests.
Project Structure
project/
src/
services/
user.service.js
user.service.test.js
utils/
validators.js
validators.test.js
components/
Button.jsx
Button.test.jsx
__tests__/
integration/
api.test.js
e2e/
checkout.test.js
__mocks__/
axios.js
localStorage.js
jest.config.js
Jest Configuration
module .exports = {
testEnvironment : 'jsdom' ,
roots : ['<rootDir>/src' ],
testMatch : ['**/__tests__/**/*.js' , '**/?(*.)+(spec|test).js' ],
collectCoverageFrom : [
'src/**/*.{js,jsx}' ,
'!src/**/*.test.{js,jsx}' ,
'!src/**/index.js' ,
],
coverageThresholds : {
global : {
branches : 80 ,
functions : 80 ,
lines : 80 ,
statements : 80 ,
},
},
setupFilesAfterEnv : ['<rootDir>/jest.setup.js' ],
moduleNameMapper : {
'^@/(.*)$' : ,
: ,
},
: {
: ,
},
: ,
: ,
};
'<rootDir>/src/$1'
'\\.(css|less|scss|sass)$'
'identity-obj-proxy'
transform
'^.+\\.jsx?$'
'babel-jest'
clearMocks
true
restoreMocks
true
Basic Testing Patterns
1. Testing Pure Functions
export function isValidEmail (email ) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ ;
return regex.test (email);
}
export function calculateTotal (items ) {
return items.reduce ((sum, item ) => sum + item.price * item.quantity , 0 );
}
export function formatCurrency (amount, currency = 'USD' ) {
return new Intl .NumberFormat ('en-US' , {
style : 'currency' ,
currency,
}).format (amount);
}
import { isValidEmail, calculateTotal, formatCurrency } from './validators' ;
describe ('Validators' , () => {
describe ('isValidEmail' , () => {
it ('should return true for valid emails' , () => {
expect (isValidEmail ('user@example.com' )).toBe (true );
expect (isValidEmail ('first.last@domain.co.uk' )).toBe (true );
expect (isValidEmail ('user+tag@example.com' )).toBe (true );
});
it ('should return false for invalid emails' , () => {
expect (isValidEmail ('' )).toBe (false );
expect (isValidEmail ('not-an-email' )).toBe (false );
expect (isValidEmail ('@missing-local.com' )).toBe (false );
expect (isValidEmail ('missing@domain' )).toBe (false );
});
});
describe ('calculateTotal' , () => {
it ('should calculate total for single item' , () => {
const items = [{ price : 10 , quantity : 2 }];
expect (calculateTotal (items)).toBe (20 );
});
it ('should calculate total for multiple items' , () => {
const items = [
{ price : 10 , quantity : 2 },
{ price : 5 , quantity : 3 },
];
expect (calculateTotal (items)).toBe (35 );
});
it ('should return 0 for empty array' , () => {
expect (calculateTotal ([])).toBe (0 );
});
});
describe ('formatCurrency' , () => {
it ('should format USD by default' , () => {
expect (formatCurrency (1234.56 )).toBe ('$1,234.56' );
});
it ('should format different currencies' , () => {
expect (formatCurrency (1234.56 , 'EUR' )).toContain ('1,234.56' );
expect (formatCurrency (1234.56 , 'GBP' )).toContain ('1,234.56' );
});
});
});
2. Testing Classes and Services
export class UserService {
constructor (apiClient, cache ) {
this .apiClient = apiClient;
this .cache = cache;
}
async getUser (id ) {
const cached = this .cache .get (`user:${id} ` );
if (cached) return JSON .parse (cached);
const user = await this .apiClient .get (`/users/${id} ` );
this .cache .set (`user:${id} ` , JSON .stringify (user));
return user;
}
async createUser (userData ) {
const user = await this .apiClient .post ('/users' , userData);
return user;
}
async deleteUser (id ) {
const user = await this .getUser (id);
if (!user) throw new Error ('User not found' );
await this .apiClient .delete (`/users/${id} ` );
this .cache .delete (`user:${id} ` );
}
}
import { UserService } from './user.service' ;
describe ('UserService' , () => {
let userService;
let mockApiClient;
let mockCache;
beforeEach (() => {
mockApiClient = {
get : jest.fn (),
post : jest.fn (),
delete : jest.fn (),
};
mockCache = {
get : jest.fn (),
set : jest.fn (),
delete : jest.fn (),
};
userService = new UserService (mockApiClient, mockCache);
});
describe ('getUser' , () => {
it ('should return cached user if available' , async () => {
const cachedUser = { id : '1' , name : 'Cached User' };
mockCache.get .mockReturnValue (JSON .stringify (cachedUser));
const result = await userService.getUser ('1' );
expect (result).toEqual (cachedUser);
expect (mockCache.get ).toHaveBeenCalledWith ('user:1' );
expect (mockApiClient.get ).not .toHaveBeenCalled ();
});
it ('should fetch from API if not cached' , async () => {
const apiUser = { id : '1' , name : 'API User' };
mockCache.get .mockReturnValue (null );
mockApiClient.get .mockResolvedValue (apiUser);
const result = await userService.getUser ('1' );
expect (result).toEqual (apiUser);
expect (mockApiClient.get ).toHaveBeenCalledWith ('/users/1' );
expect (mockCache.set ).toHaveBeenCalledWith ('user:1' , JSON .stringify (apiUser));
});
});
describe ('createUser' , () => {
it ('should create a new user' , async () => {
const newUser = { id : '2' , name : 'New User' };
mockApiClient.post .mockResolvedValue (newUser);
const result = await userService.createUser ({ name : 'New User' });
expect (result).toEqual (newUser);
expect (mockApiClient.post ).toHaveBeenCalledWith ('/users' , { name : 'New User' });
});
});
describe ('deleteUser' , () => {
it ('should delete user and clear cache' , async () => {
const user = { id : '1' , name : 'User' };
mockCache.get .mockReturnValue (JSON .stringify (user));
await userService.deleteUser ('1' );
expect (mockApiClient.delete ).toHaveBeenCalledWith ('/users/1' );
expect (mockCache.delete ).toHaveBeenCalledWith ('user:1' );
});
it ('should throw error when user not found' , async () => {
mockCache.get .mockReturnValue (null );
mockApiClient.get .mockResolvedValue (null );
await expect (userService.deleteUser ('nonexistent' )).rejects .toThrow ('User not found' );
});
});
});
Async Testing Patterns
1. Promises and Async/Await describe ('Async operations' , () => {
it ('should handle resolved promises' , async () => {
const result = await fetchData ();
expect (result.status ).toBe ('success' );
});
it ('should handle rejected promises' , async () => {
await expect (fetchData ('invalid' )).rejects .toThrow ('Invalid ID' );
});
it ('should use resolves matcher' , async () => {
await expect (fetchUser ('1' )).resolves .toEqual ({ id : '1' , name : 'User' });
});
it ('should use rejects matcher' , async () => {
await expect (fetchUser ('invalid' )).rejects .toThrow ('Not found' );
});
it ('should test callback-based async code' , (done ) => {
fetchDataCallback ('1' , (err, data ) => {
expect (err).toBeNull ();
expect (data.id ).toBe ('1' );
done ();
});
});
});
2. Testing Timers describe ('Timer functions' , () => {
beforeEach (() => {
jest.useFakeTimers ();
});
afterEach (() => {
jest.useRealTimers ();
});
it ('should debounce function calls' , () => {
const fn = jest.fn ();
const debounced = debounce (fn, 300 );
debounced ();
debounced ();
debounced ();
expect (fn).not .toHaveBeenCalled ();
jest.advanceTimersByTime (300 );
expect (fn).toHaveBeenCalledTimes (1 );
});
it ('should throttle function calls' , () => {
const fn = jest.fn ();
const throttled = throttle (fn, 100 );
throttled ();
expect (fn).toHaveBeenCalledTimes (1 );
throttled ();
expect (fn).toHaveBeenCalledTimes (1 );
jest.advanceTimersByTime (100 );
throttled ();
expect (fn).toHaveBeenCalledTimes (2 );
});
});
Mocking Patterns
1. Function Mocking describe ('Function mocking' , () => {
it ('should mock a simple function' , () => {
const mockFn = jest.fn ();
mockFn.mockReturnValue (42 );
expect (mockFn ()).toBe (42 );
expect (mockFn).toHaveBeenCalledTimes (1 );
});
it ('should mock with implementation' , () => {
const mockFn = jest.fn ((x ) => x * 2 );
expect (mockFn (5 )).toBe (10 );
expect (mockFn).toHaveBeenCalledWith (5 );
});
it ('should mock resolved values' , async () => {
const mockFn = jest.fn ();
mockFn.mockResolvedValue ({ id : 1 , name : 'Test' });
const result = await mockFn ();
expect (result).toEqual ({ id : 1 , name : 'Test' });
});
it ('should mock different return values' , () => {
const mockFn = jest.fn ();
mockFn
.mockReturnValueOnce (1 )
.mockReturnValueOnce (2 )
.mockReturnValue (3 );
expect (mockFn ()).toBe (1 );
expect (mockFn ()).toBe (2 );
expect (mockFn ()).toBe (3 );
expect (mockFn ()).toBe (3 );
});
});
2. Module Mocking
module .exports = {
get : jest.fn (() => Promise .resolve ({ data : {} })),
post : jest.fn (() => Promise .resolve ({ data : {} })),
put : jest.fn (() => Promise .resolve ({ data : {} })),
delete : jest.fn (() => Promise .resolve ({ data : {} })),
};
jest.mock ('axios' );
import axios from 'axios' ;
import { fetchUsers } from './api' ;
describe ('API functions' , () => {
it ('should fetch users' , async () => {
const mockUsers = [{ id : 1 , name : 'User 1' }];
axios.get .mockResolvedValue ({ data : mockUsers });
const users = await fetchUsers ();
expect (users).toEqual (mockUsers);
expect (axios.get ).toHaveBeenCalledWith ('/api/users' );
});
});
3. Partial Module Mocking jest.mock ('./utils' , () => ({
...jest.requireActual ('./utils' ),
fetchData : jest.fn (),
}));
import { fetchData, formatData } from './utils' ;
test ('should use real formatData but mocked fetchData' , () => {
fetchData.mockResolvedValue ({ raw : 'data' });
});
4. Spying describe ('Spying' , () => {
it ('should spy on console.error' , () => {
const spy = jest.spyOn (console , 'error' ).mockImplementation (() => {});
console .error ('Test error' );
expect (spy).toHaveBeenCalledWith ('Test error' );
spy.mockRestore ();
});
it ('should spy on object methods' , () => {
const obj = {
method : (x ) => x * 2 ,
};
const spy = jest.spyOn (obj, 'method' );
const result = obj.method (5 );
expect (result).toBe (10 );
expect (spy).toHaveBeenCalledWith (5 );
});
});
DOM Testing with React Testing Library
export function Button ({ onClick, disabled, children, variant = 'primary' } ) {
return (
<button
onClick ={onClick}
disabled ={disabled}
className ={ `btn btn- ${variant }`}
>
{children}
</button >
);
}
import { render, screen, fireEvent } from '@testing-library/react' ;
import { Button } from './Button' ;
describe ('Button' , () => {
it ('should render with text' , () => {
render (<Button > Click me</Button > );
expect (screen.getByText ('Click me' )).toBeInTheDocument ();
});
it ('should handle click events' , () => {
const onClick = jest.fn ();
render (<Button onClick ={onClick} > Click me</Button > );
fireEvent.click (screen.getByText ('Click me' ));
expect (onClick).toHaveBeenCalledTimes (1 );
});
it ('should be disabled when disabled prop is true' , () => {
render (<Button disabled > Click me</Button > );
expect (screen.getByRole ('button' )).toBeDisabled ();
});
it ('should apply variant class' , () => {
render (<Button variant ="secondary" > Click me</Button > );
expect (screen.getByRole ('button' )).toHaveClass ('btn-secondary' );
});
});
Snapshot Testing describe ('Snapshot tests' , () => {
it ('should match snapshot' , () => {
const data = {
id : 1 ,
name : 'Test User' ,
roles : ['admin' , 'user' ],
};
expect (data).toMatchSnapshot ();
});
it ('should match inline snapshot' , () => {
const formatted = formatUserName ({ first : 'John' , last : 'Doe' });
expect (formatted).toMatchInlineSnapshot (`"John Doe"` );
});
it ('should snapshot React component' , () => {
const { container } = render (<Button > Click me</Button > );
expect (container).toMatchSnapshot ();
});
});
Test Organization Patterns
1. Describe Blocks describe ('UserManager' , () => {
describe ('creating users' , () => {
it ('should create user with valid data' , () => {});
it ('should reject duplicate emails' , () => {});
});
describe ('retrieving users' , () => {
it ('should get user by id' , () => {});
it ('should return null for non-existent user' , () => {});
});
describe ('deleting users' , () => {
it ('should delete existing user' , () => {});
it ('should throw error for non-existent user' , () => {});
});
});
2. Setup and Teardown describe ('Test suite with setup' , () => {
let db;
beforeAll (() => {
db = createDatabase ();
});
afterAll (() => {
db.close ();
});
beforeEach (() => {
db.clear ();
});
afterEach (() => {
jest.clearAllMocks ();
});
it ('test 1' , () => {});
it ('test 2' , () => {});
});
Testing Mocha Style const { expect } = require ('chai' );
const sinon = require ('sinon' );
describe ('Mocha tests' , () => {
it ('should test with Chai assertions' , () => {
expect (true ).to .be .true ;
expect ([1 , 2 , 3 ]).to .have .lengthOf (3 );
expect ({ name : 'Test' }).to .have .property ('name' , 'Test' );
});
it ('should use Sinon spies' , () => {
const callback = sinon.spy ();
const obj = { method : callback };
obj.method ('arg1' , 'arg2' );
expect (callback.calledOnce ).to .be .true ;
expect (callback.calledWith ('arg1' , 'arg2' )).to .be .true ;
});
it ('should use Sinon stubs' , () => {
const stub = sinon.stub ().returns (42 );
expect (stub ()).to .equal (42 );
expect (stub.called ).to .be .true ;
});
});
Best Practices
Test behavior, not implementation -- Refactoring should not break tests.
Use descriptive test names -- Tests should read like specifications.
One assertion focus per test -- Each test verifies one concept.
Keep tests simple -- Complex tests are hard to maintain.
Use beforeEach for setup -- Ensure clean state for every test.
Mock at boundaries -- Mock external dependencies, not internal functions.
Avoid test interdependence -- Tests should run in any order.
Use data-driven tests -- Parametrize similar tests.
Test edge cases -- Empty arrays, null, undefined, zero.
Keep tests fast -- Slow tests discourage running them frequently.
Anti-Patterns to Avoid
Testing private methods -- Test public API only.
Mocking everything -- Test real code paths when possible.
Shared mutable state -- Use beforeEach to reset state.
Giant test files -- Split by feature or module.
No assertions -- Every test needs at least one expect.
Using .only or .skip in commits -- Clean up before pushing.
Hardcoded delays -- Use fake timers instead.
Testing framework code -- Don't test that Array.push works.
Ignoring test failures -- Fix or delete failing tests.
Not using coverage -- Track what's tested and what's not.
Running Tests
npm test
npm test -- --watch
npm test user.service.test.js
npm test -- --coverage
npm test -- --testNamePattern="user"
npm test -- --updateSnapshot
CI=true npm test
JavaScript testing is about building confidence in your code. Write tests that give you the freedom to refactor and the safety to ship with confidence.