Skills · Coding

Javascript Testing Patterns

Unverified22/40

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.

Originally by wshobson · MIT

Claude CodePartialHas SKILL.md but declares no allowed-tools — Claude Code will ask for permission each time
CursorPartialPlain prose you can paste in — but no Cursor rules file
CodexPartialPlain prose you can paste in — but no AGENTS.md
Gemini CLIPartialPlain prose you can paste in
CopilotPartialPlain prose you can paste in — but no Copilot instructions file
npx agentalley add javascript-testing-patterns

This command does not work yet — the CLI is still being built. Until then, use Raw in the reader below to take the file.

Who is stuck, and on what

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.

The whole source

No sign-in, no blur, nothing truncated
javascript-testing-patterns/SKILL.md538 lines14.3 KBRawView on GitHub
Frontmatter — 2 properties
namejavascript-testing-patterns
descriptionImplement 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.
1---
2name: javascript-testing-patterns
3description: 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.
4---A5No allowed-tools declared — no way to tell what this skill may touch
5 
6# JavaScript Testing Patterns
7 
8Comprehensive guide for implementing robust testing strategies in JavaScript/TypeScript applications using modern testing frameworks and best practices.
9 
10## When to Use This Skill
11 
12- Setting up test infrastructure for new projects
13- Writing unit tests for functions and classes
14- Creating integration tests for APIs and services
15- Implementing end-to-end tests for user flows
16- Mocking external dependencies and APIs
17- Testing React, Vue, or other frontend components
18- Implementing test-driven development (TDD)
19- Setting up continuous testing in CI/CD pipelines
20 
21## Testing Frameworks
22 
23### Jest - Full-Featured Testing Framework
24 
25**Setup:**
26 
27```typescript
28// jest.config.ts
29import type { Config } from "jest";
30 
31const config: Config = {
32 preset: "ts-jest",
33 testEnvironment: "node",
34 roots: ["<rootDir>/src"],
35 testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
36 collectCoverageFrom: [
37 "src/**/*.ts",
38 "!src/**/*.d.ts",
39 "!src/**/*.interface.ts",
40 ],
41 coverageThreshold: {
42 global: {
43 branches: 80,
44 functions: 80,
45 lines: 80,
46 statements: 80,
47 },
48 },
49 setupFilesAfterEnv: ["<rootDir>/src/test/setup.ts"],
50};
51 
52export default config;
53```
54 
55### Vitest - Fast, Vite-Native Testing
56 
57**Setup:**
58 
59```typescript
60// vitest.config.ts
61import { defineConfig } from "vitest/config";
62 
63export default defineConfig({
64 test: {
65 globals: true,
66 environment: "node",
67 coverage: {
68 provider: "v8",
69 reporter: ["text", "json", "html"],
70 exclude: ["**/*.d.ts", "**/*.config.ts", "**/dist/**"],
71 },
72 setupFiles: ["./src/test/setup.ts"],
73 },
74});
75```
76 
77## Unit Testing Patterns
78 
79### Pattern 1: Testing Pure Functions
80 
81```typescript
82// utils/calculator.ts
83export function add(a: number, b: number): number {
84 return a + b;
85}
86 
87export function divide(a: number, b: number): number {
88 if (b === 0) {
89 throw new Error("Division by zero");
90 }
91 return a / b;
92}
93 
94// utils/calculator.test.ts
95import { describe, it, expect } from "vitest";
96import { add, divide } from "./calculator";
97 
98describe("Calculator", () => {
99 describe("add", () => {
100 it("should add two positive numbers", () => {
101 expect(add(2, 3)).toBe(5);
102 });
103 
104 it("should add negative numbers", () => {
105 expect(add(-2, -3)).toBe(-5);
106 });
107 
108 it("should handle zero", () => {
109 expect(add(0, 5)).toBe(5);
110 expect(add(5, 0)).toBe(5);
111 });
112 });
113 
114 describe("divide", () => {
115 it("should divide two numbers", () => {
116 expect(divide(10, 2)).toBe(5);
117 });
118 
119 it("should handle decimal results", () => {
120 expect(divide(5, 2)).toBe(2.5);
121 });
122 
123 it("should throw error when dividing by zero", () => {
124 expect(() => divide(10, 0)).toThrow("Division by zero");
125 });
126 });
127});
128```
129 
130### Pattern 2: Testing Classes
131 
132```typescript
133// services/user.service.ts
134export class UserService {
135 private users: Map<string, User> = new Map();
136 
137 create(user: User): User {
138 if (this.users.has(user.id)) {
139 throw new Error("User already exists");
140 }
141 this.users.set(user.id, user);
142 return user;
143 }
144 
145 findById(id: string): User | undefined {
146 return this.users.get(id);
147 }
148 
149 update(id: string, updates: Partial<User>): User {
150 const user = this.users.get(id);
151 if (!user) {
152 throw new Error("User not found");
153 }
154 const updated = { ...user, ...updates };
155 this.users.set(id, updated);
156 return updated;
157 }
158 
159 delete(id: string): boolean {
160 return this.users.delete(id);
161 }
162}
163 
164// services/user.service.test.ts
165import { describe, it, expect, beforeEach } from "vitest";
166import { UserService } from "./user.service";
167 
168describe("UserService", () => {
169 let service: UserService;
170 
171 beforeEach(() => {
172 service = new UserService();
173 });
174 
175 describe("create", () => {
176 it("should create a new user", () => {
177 const user = { id: "1", name: "John", email: "[email protected]" };
178 const created = service.create(user);
179 
180 expect(created).toEqual(user);
181 expect(service.findById("1")).toEqual(user);
182 });
183 
184 it("should throw error if user already exists", () => {
185 const user = { id: "1", name: "John", email: "[email protected]" };
186 service.create(user);
187 
188 expect(() => service.create(user)).toThrow("User already exists");
189 });
190 });
191 
192 describe("update", () => {
193 it("should update existing user", () => {
194 const user = { id: "1", name: "John", email: "[email protected]" };
195 service.create(user);
196 
197 const updated = service.update("1", { name: "Jane" });
198 
199 expect(updated.name).toBe("Jane");
200 expect(updated.email).toBe("[email protected]");
201 });
202 
203 it("should throw error if user not found", () => {
204 expect(() => service.update("999", { name: "Jane" })).toThrow(
205 "User not found",
206 );
207 });
208 });
209});
210```
211 
212### Pattern 3: Testing Async Functions
213 
214```typescript
215// services/api.service.ts
216export class ApiService {
217 async fetchUser(id: string): Promise<User> {
218 const response = await fetch(`https://api.example.com/users/${id}`);A4This skill pulls in web or user content but never says to treat that content as data. A signal, not proof.
219 if (!response.ok) {
220 throw new Error("User not found");
221 }
222 return response.json();
223 }
224 
225 async createUser(user: CreateUserDTO): Promise<User> {
226 const response = await fetch("https://api.example.com/users", {
227 method: "POST",
228 headers: { "Content-Type": "application/json" },
229 body: JSON.stringify(user),
230 });
231 return response.json();
232 }
233}
234 
235// services/api.service.test.ts
236import { describe, it, expect, vi, beforeEach } from "vitest";
237import { ApiService } from "./api.service";
238 
239// Mock fetch globally
240global.fetch = vi.fn();
241 
242describe("ApiService", () => {
243 let service: ApiService;
244 
245 beforeEach(() => {
246 service = new ApiService();
247 vi.clearAllMocks();
248 });
249 
250 describe("fetchUser", () => {
251 it("should fetch user successfully", async () => {
252 const mockUser = { id: "1", name: "John", email: "[email protected]" };
253 
254 (fetch as any).mockResolvedValueOnce({
255 ok: true,
256 json: async () => mockUser,
257 });
258 
259 const user = await service.fetchUser("1");
260 
261 expect(user).toEqual(mockUser);
262 expect(fetch).toHaveBeenCalledWith("https://api.example.com/users/1");
263 });
264 
265 it("should throw error if user not found", async () => {
266 (fetch as any).mockResolvedValueOnce({
267 ok: false,
268 });
269 
270 await expect(service.fetchUser("999")).rejects.toThrow("User not found");
271 });
272 });
273 
274 describe("createUser", () => {
275 it("should create user successfully", async () => {
276 const newUser = { name: "John", email: "[email protected]" };
277 const createdUser = { id: "1", ...newUser };
278 
279 (fetch as any).mockResolvedValueOnce({
280 ok: true,
281 json: async () => createdUser,
282 });
283 
284 const user = await service.createUser(newUser);
285 
286 expect(user).toEqual(createdUser);
287 expect(fetch).toHaveBeenCalledWith(
288 "https://api.example.com/users",
289 expect.objectContaining({
290 method: "POST",
291 body: JSON.stringify(newUser),
292 }),
293 );
294 });
295 });
296});
297```
298 
299## Mocking Patterns
300 
301### Pattern 1: Mocking Modules
302 
303```typescript
304// services/email.service.ts
305import nodemailer from "nodemailer";
306 
307export class EmailService {
308 private transporter = nodemailer.createTransport({
309 host: process.env.SMTP_HOST,
310 port: 587,
311 auth: {
312 user: process.env.SMTP_USER,
313 pass: process.env.SMTP_PASS,
314 },
315 });
316 
317 async sendEmail(to: string, subject: string, html: string) {
318 await this.transporter.sendMail({
319 from: process.env.EMAIL_FROM,
320 to,
321 subject,
322 html,
323 });
324 }
325}
326 
327// services/email.service.test.ts
328import { describe, it, expect, vi, beforeEach } from "vitest";
329import { EmailService } from "./email.service";
330 
331vi.mock("nodemailer", () => ({
332 default: {
333 createTransport: vi.fn(() => ({
334 sendMail: vi.fn().mockResolvedValue({ messageId: "123" }),
335 })),
336 },
337}));
338 
339describe("EmailService", () => {
340 let service: EmailService;
341 
342 beforeEach(() => {
343 service = new EmailService();
344 });
345 
346 it("should send email successfully", async () => {
347 await service.sendEmail(
348 "[email protected]",
349 "Test Subject",
350 "<p>Test Body</p>",
351 );
352 
353 expect(service["transporter"].sendMail).toHaveBeenCalledWith(
354 expect.objectContaining({
355 to: "[email protected]",
356 subject: "Test Subject",
357 }),
358 );
359 });
360});
361```
362 
363### Pattern 2: Dependency Injection for Testing
364 
365```typescript
366// services/user.service.ts
367export interface IUserRepository {
368 findById(id: string): Promise<User | null>;
369 create(user: User): Promise<User>;
370}
371 
372export class UserService {
373 constructor(private userRepository: IUserRepository) {}
374 
375 async getUser(id: string): Promise<User> {
376 const user = await this.userRepository.findById(id);
377 if (!user) {
378 throw new Error("User not found");
379 }
380 return user;
381 }
382 
383 async createUser(userData: CreateUserDTO): Promise<User> {
384 // Business logic here
385 const user = { id: generateId(), ...userData };
386 return this.userRepository.create(user);
387 }
388}
389 
390// services/user.service.test.ts
391import { describe, it, expect, vi, beforeEach } from "vitest";
392import { UserService, IUserRepository } from "./user.service";
393 
394describe("UserService", () => {
395 let service: UserService;
396 let mockRepository: IUserRepository;
397 
398 beforeEach(() => {
399 mockRepository = {
400 findById: vi.fn(),
401 create: vi.fn(),
402 };
403 service = new UserService(mockRepository);
404 });
405 
406 describe("getUser", () => {
407 it("should return user if found", async () => {
408 const mockUser = { id: "1", name: "John", email: "[email protected]" };
409 vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);
410 
411 const user = await service.getUser("1");
412 
413 expect(user).toEqual(mockUser);
414 expect(mockRepository.findById).toHaveBeenCalledWith("1");
415 });
416 
417 it("should throw error if user not found", async () => {
418 vi.mocked(mockRepository.findById).mockResolvedValue(null);
419 
420 await expect(service.getUser("999")).rejects.toThrow("User not found");
421 });
422 });
423 
424 describe("createUser", () => {
425 it("should create user successfully", async () => {
426 const userData = { name: "John", email: "[email protected]" };
427 const createdUser = { id: "1", ...userData };
428 
429 vi.mocked(mockRepository.create).mockResolvedValue(createdUser);
430 
431 const user = await service.createUser(userData);
432 
433 expect(user).toEqual(createdUser);
434 expect(mockRepository.create).toHaveBeenCalled();
435 });
436 });
437});
438```
439 
440### Pattern 3: Spying on Functions
441 
442```typescript
443// utils/logger.ts
444export const logger = {
445 info: (message: string) => console.log(`INFO: ${message}`),
446 error: (message: string) => console.error(`ERROR: ${message}`),
447};
448 
449// services/order.service.ts
450import { logger } from "../utils/logger";
451 
452export class OrderService {
453 async processOrder(orderId: string): Promise<void> {
454 logger.info(`Processing order ${orderId}`);
455 // Process order logic
456 logger.info(`Order ${orderId} processed successfully`);
457 }
458}
459 
460// services/order.service.test.ts
461import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
462import { OrderService } from "./order.service";
463import { logger } from "../utils/logger";
464 
465describe("OrderService", () => {
466 let service: OrderService;
467 let loggerSpy: any;
468 
469 beforeEach(() => {
470 service = new OrderService();
471 loggerSpy = vi.spyOn(logger, "info");
472 });
473 
474 afterEach(() => {
475 loggerSpy.mockRestore();
476 });
477 
478 it("should log order processing", async () => {
479 await service.processOrder("123");
480 
481 expect(loggerSpy).toHaveBeenCalledWith("Processing order 123");
482 expect(loggerSpy).toHaveBeenCalledWith("Order 123 processed successfully");
483 expect(loggerSpy).toHaveBeenCalledTimes(2);
484 });
485});
486```
487 
488## Integration Testing
489 
490Integration tests verify real database operations and HTTP endpoints using `supertest` and a test database instance. Always truncate tables in `beforeEach` and tear down in `afterAll`.A1Empties a table
491 
492For full API integration test examples (supertest + PostgreSQL) and database repository integration tests, see [references/advanced-testing-patterns.md](references/advanced-testing-patterns.md).
493 
494## Frontend Testing with Testing Library
495 
496Test React components by rendering them and querying by role, placeholder, or test ID. Test hooks with `renderHook` + `act`. Prefer semantic queries (`getByRole`, `getByPlaceholderText`) over `data-testid`.
497 
498For complete React component test examples (UserForm, hooks with `renderHook`/`act`), see [references/advanced-testing-patterns.md](references/advanced-testing-patterns.md).
499 
500## Test Fixtures and Factories
501 
502Use `@faker-js/faker` to generate realistic test data factories. Factories accept optional `overrides` so tests can set only the fields they care about:
503 
504```typescript
505// tests/fixtures/user.fixture.ts
506import { faker } from "@faker-js/faker";
507 
508export function createUserFixture(overrides?: Partial<User>): User {
509 return {
510 id: faker.string.uuid(),
511 name: faker.person.fullName(),
512 email: faker.internet.email(),
513 createdAt: faker.date.past(),
514 ...overrides,
515 };
516}
517```
518 
519For snapshot testing, coverage configuration, test organization patterns, promise testing, and timer mocking, see [references/advanced-testing-patterns.md](references/advanced-testing-patterns.md).
520 
521## Best Practices
522 
5231. **Follow AAA Pattern**: Arrange, Act, Assert
5242. **One assertion per test**: Or logically related assertions
5253. **Descriptive test names**: Should describe what is being tested
5264. **Use beforeEach/afterEach**: For setup and teardown
5275. **Mock external dependencies**: Keep tests isolated
5286. **Test edge cases**: Not just happy paths
5297. **Avoid implementation details**: Test behavior, not implementation
5308. **Use test factories**: For consistent test data
5319. **Keep tests fast**: Mock slow operations
53210. **Write tests first (TDD)**: When possible
53311. **Maintain test coverage**: Aim for 80%+ coverage
53412. **Use TypeScript**: For type-safe tests
53513. **Test error handling**: Not just success cases
53614. **Use data-testid sparingly**: Prefer semantic queries
53715. **Clean up after tests**: Prevent test pollution
538 

Reviews

Installed this one?Write the first review and take the Trailblazer badge.

Reviews only open after a real install, so this is empty — and we leave it empty rather than invent one.

Alternatives

Also in Coding