Implement comprehensive testing strategies using Jest, Vitest, and Testing Library for unit tests, integration tests, and end-to-end testing with mocking, fixtures, and test-driven development. Use when writing JavaScript/TypeScript tests, setting up test infrastructure, or implementing TDD/BDD workflows.
Comprehensive testing strategies for JavaScript/TypeScript applications.
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
coverageThreshold: {
global: { branches: 80, functions: 80, lines: 80, statements: 80 },
},
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
};
export default config;
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
setupFiles: ['./src/test/setup.ts'],
},
});
import { describe, it, expect } from 'vitest';
describe('Calculator', () => {
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle zero', () => {
expect(add(0, 5)).toBe(5);
});
});
describe('divide', () => {
it('should throw error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
});
});
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
describe('create', () => {
it('should create a new user', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
const created = service.create(user);
expect(created).toEqual(user);
expect(service.findById('1')).toEqual(user);
});
it('should throw error if user already exists', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
service.create(user);
expect(() => service.create(user)).toThrow('User already exists');
});
});
});
describe('ApiService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should fetch user successfully', async () => {
const mockUser = { id: '1', name: 'John' };
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => mockUser,
});
const user = await service.fetchUser('1');
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
it('should throw error if user not found', async () => {
global.fetch = vi.fn().mockResolvedValueOnce({ ok: false });
await expect(service.fetchUser('999')).rejects.toThrow('User not found');
});
});
vi.mock('nodemailer', () => ({
default: {
createTransport: vi.fn(() => ({
sendMail: vi.fn().mockResolvedValue({ messageId: '123' }),
})),
},
}));
describe('UserService', () => {
let service: UserService;
let mockRepository: IUserRepository;
beforeEach(() => {
mockRepository = {
findById: vi.fn(),
create: vi.fn(),
};
service = new UserService(mockRepository);
});
it('should return user if found', async () => {
const mockUser = { id: '1', name: 'John' };
vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);
const user = await service.getUser('1');
expect(user).toEqual(mockUser);
expect(mockRepository.findById).toHaveBeenCalledWith('1');
});
});
let loggerSpy: any;
beforeEach(() => {
loggerSpy = vi.spyOn(logger, 'info');
});
afterEach(() => {
loggerSpy.mockRestore();
});
it('should log order processing', async () => {
await service.processOrder('123');
expect(loggerSpy).toHaveBeenCalledWith('Processing order 123');
});
import { render, screen, fireEvent } from '@testing-library/react';
describe('UserForm', () => {
it('should render form inputs', () => {
render(<UserForm onSubmit={vi.fn()} />);
expect(screen.getByPlaceholderText('Name')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
});
it('should call onSubmit with form data', () => {
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByTestId('name-input'), {
target: { value: 'John Doe' },
});
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
expect(onSubmit).toHaveBeenCalledWith({ name: 'John Doe' });
});
});
import { renderHook, act } from '@testing-library/react';
describe('useCounter', () => {
it('should increment count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
import { faker } from '@faker-js/faker';
export function createUserFixture(overrides?: Partial<User>): User {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
...overrides,
};
}
// Testing promises
await expect(service.fetchUser('invalid')).rejects.toThrow('Not found');
// Testing timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.useRealTimers();
// Snapshot testing
expect(container.firstChild).toMatchSnapshot();
For comprehensive patterns, see:
npx skills add thapaliyabikendra/javascript-testing-patterns下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer