Implement comprehensive testing strategies using Jest

Javascript testing patterns

by wshobson · MIT license · ★ 39,857 on GitHub · updated Sep 21

Javascript testing patterns cover

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…

---
name: javascript-testing-patterns
description: 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.
---

# JavaScript Testing Patterns

Comprehensive guide for implementing robust testing strategies in JavaScript/TypeScript applications using modern testing frameworks and best practices.

## When to Use This Skill

- Setting up test infrastructure for new projects
- Writing unit tests for functions and classes
- Creating integration tests for APIs and services
- Implementing end-to-end tests for user flows
- Mocking external dependencies and APIs
- Testing React, Vue, or other frontend components
- Implementing test-driven development (TDD)

Overview

Comprehensive guide for implementing robust testing strategies in JavaScript/TypeScript applications using modern testing frameworks and best practices.

Manual install (npx, ZIP)
Claude Codenpx skills add wshobson/agents --skill javascript-testing-patterns -g -a claude-code
Claude Code (manual)mkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/wshobson/agents/tar.gz/4236bb91f839 \ | tar -xz -C ~/.claude/skills --strip-components=4 agents-4236bb91f839/plugins/javascript-typescript/skills/javascript-testing-patterns
Windows (PowerShell)New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/89f7bd5028f91efc5615/javascript-testing-patterns.zip" -OutFile "$env:TEMP\javascript-testing-patterns.zip"; Expand-Archive "$env:TEMP\javascript-testing-patterns.zip" -DestinationPath "$HOME\.claude\skills" -Force
Claude.ai / desktopDownload the ZIP and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md14.3 KB
  • references/1 file · 12.5 KB
Read the files here
wshobson/4236bb92 files
SKILL.md
Show the full text538 lines

JavaScript Testing Patterns

Comprehensive guide for implementing robust testing strategies in JavaScript/TypeScript applications using modern testing frameworks and best practices.

When to Use This Skill

  • Setting up test infrastructure for new projects
  • Writing unit tests for functions and classes
  • Creating integration tests for APIs and services
  • Implementing end-to-end tests for user flows
  • Mocking external dependencies and APIs
  • Testing React, Vue, or other frontend components
  • Implementing test-driven development (TDD)
  • Setting up continuous testing in CI/CD pipelines

Testing Frameworks

Setup:

// 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",
    "!src/**/*.interface.ts",
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  setupFilesAfterEnv: ["<rootDir>/src/test/setup.ts"],
};

export default config;
Vitest - Fast, Vite-Native Testing

Setup:

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    coverage: {
      provider: "v8",
      reporter: ["text", "json", "html"],
      exclude: ["**/*.d.ts", "**/*.config.ts", "**/dist/**"],
    },
    setupFiles: ["./src/test/setup.ts"],
  },
});

Unit Testing Patterns

Pattern 1: Testing Pure Functions
// utils/calculator.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

// utils/calculator.test.ts
import { describe, it, expect } from "vitest";
import { add, divide } from "./calculator";

describe("Calculator", () => {
  describe("add", () => {
    it("should add two positive numbers", () => {
      expect(add(2, 3)).toBe(5);
    });

    it("should add negative numbers", () => {
      expect(add(-2, -3)).toBe(-5);
    });

    it("should handle zero", () => {
      expect(add(0, 5)).toBe(5);
      expect(add(5, 0)).toBe(5);
    });
  });

  describe("divide", () => {
    it("should divide two numbers", () => {
      expect(divide(10, 2)).toBe(5);
    });

    it("should handle decimal results", () => {
      expect(divide(5, 2)).toBe(2.5);
    });

    it("should throw error when dividing by zero", () => {
      expect(() => divide(10, 0)).toThrow("Division by zero");
    });
  });
});
Pattern 2: Testing Classes
// services/user.service.ts
export class UserService {
  private users: Map<string, User> = new Map();

  create(user: User): User {
    if (this.users.has(user.id)) {
      throw new Error("User already exists");
    }
    this.users.set(user.id, user);
    return user;
  }

  findById(id: string): User | undefined {
    return this.users.get(id);
  }

  update(id: string, updates: Partial<User>): User {
    const user = this.users.get(id);
    if (!user) {
      throw new Error("User not found");
    }
    const updated = { ...user, ...updates };
    this.users.set(id, updated);
    return updated;
  }

  delete(id: string): boolean {
    return this.users.delete(id);
  }
}

// services/user.service.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { UserService } from "./user.service";

describe("UserService", () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
  });

  describe("create", () => {
    it("should create a new user", () => {
      const user = { id: "1", name: "John", email: "[email protected]" };
      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: "[email protected]" };
      service.create(user);

      expect(() => service.create(user)).toThrow("User already exists");
    });
  });

  describe("update", () => {
    it("should update existing user", () => {
      const user = { id: "1", name: "John", email: "[email protected]" };
      service.create(user);

      const updated = service.update("1", { name: "Jane" });

      expect(updated.name).toBe("Jane");
      expect(updated.email).toBe("[email protected]");
    });

    it("should throw error if user not found", () => {
      expect(() => service.update("999", { name: "Jane" })).toThrow(
        "User not found",
      );
    });
  });
});
Pattern 3: Testing Async Functions
// services/api.service.ts
export class ApiService {
  async fetchUser(id: string): Promise<User> {
    const response = await fetch(`https://api.example.com/users/${id}`);
    if (!response.ok) {
      throw new Error("User not found");
    }
    return response.json();
  }

  async createUser(user: CreateUserDTO): Promise<User> {
    const response = await fetch("https://api.example.com/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(user),
    });
    return response.json();
  }
}

// services/api.service.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { ApiService } from "./api.service";

// Mock fetch globally
global.fetch = vi.fn();

describe("ApiService", () => {
  let service: ApiService;

  beforeEach(() => {
    service = new ApiService();
    vi.clearAllMocks();
  });

  describe("fetchUser", () => {
    it("should fetch user successfully", async () => {
      const mockUser = { id: "1", name: "John", email: "[email protected]" };

      (fetch as any).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 () => {
      (fetch as any).mockResolvedValueOnce({
        ok: false,
      });

      await expect(service.fetchUser("999")).rejects.toThrow("User not found");
    });
  });

  describe("createUser", () => {
    it("should create user successfully", async () => {
      const newUser = { name: "John", email: "[email protected]" };
      const createdUser = { id: "1", ...newUser };

      (fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => createdUser,
      });

      const user = await service.createUser(newUser);

      expect(user).toEqual(createdUser);
      expect(fetch).toHaveBeenCalledWith(
        "https://api.example.com/users",
        expect.objectContaining({
          method: "POST",
          body: JSON.stringify(newUser),
        }),
      );
    });
  });
});

Mocking Patterns

Pattern 1: Mocking Modules
// services/email.service.ts
import nodemailer from "nodemailer";

export class EmailService {
  private transporter = nodemailer.createTransport({
    host: process.env.SMTP_HOST,
    port: 587,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
    },
  });

  async sendEmail(to: string, subject: string, html: string) {
    await this.transporter.sendMail({
      from: process.env.EMAIL_FROM,
      to,
      subject,
      html,
    });
  }
}

// services/email.service.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { EmailService } from "./email.service";

vi.mock("nodemailer", () => ({
  default: {
    createTransport: vi.fn(() => ({
      sendMail: vi.fn().mockResolvedValue({ messageId: "123" }),
    })),
  },
}));

describe("EmailService", () => {
  let service: EmailService;

  beforeEach(() => {
    service = new EmailService();
  });

  it("should send email successfully", async () => {
    await service.sendEmail(
      "[email protected]",
      "Test Subject",
      "<p>Test Body</p>",
    );

    expect(service["transporter"].sendMail).toHaveBeenCalledWith(
      expect.objectContaining({
        to: "[email protected]",
        subject: "Test Subject",
      }),
    );
  });
});
Pattern 2: Dependency Injection for Testing
// services/user.service.ts
export interface IUserRepository {
  findById(id: string): Promise<User | null>;
  create(user: User): Promise<User>;
}

export class UserService {
  constructor(private userRepository: IUserRepository) {}

  async getUser(id: string): Promise<User> {
    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new Error("User not found");
    }
    return user;
  }

  async createUser(userData: CreateUserDTO): Promise<User> {
    // Business logic here
    const user = { id: generateId(), ...userData };
    return this.userRepository.create(user);
  }
}

// services/user.service.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserService, IUserRepository } from "./user.service";

describe("UserService", () => {
  let service: UserService;
  let mockRepository: IUserRepository;

  beforeEach(() => {
    mockRepository = {
      findById: vi.fn(),
      create: vi.fn(),
    };
    service = new UserService(mockRepository);
  });

  describe("getUser", () => {
    it("should return user if found", async () => {
      const mockUser = { id: "1", name: "John", email: "[email protected]" };
      vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);

      const user = await service.getUser("1");

      expect(user).toEqual(mockUser);
      expect(mockRepository.findById).toHaveBeenCalledWith("1");
    });

    it("should throw error if user not found", async () => {
      vi.mocked(mockRepository.findById).mockResolvedValue(null);

      await expect(service.getUser("999")).rejects.toThrow("User not found");
    });
  });

  describe("createUser", () => {
    it("should create user successfully", async () => {
      const userData = { name: "John", email: "[email protected]" };
      const createdUser = { id: "1", ...userData };

      vi.mocked(mockRepository.create).mockResolvedValue(createdUser);

      const user = await service.createUser(userData);

      expect(user).toEqual(createdUser);
      expect(mockRepository.create).toHaveBeenCalled();
    });
  });
});
Pattern 3: Spying on Functions
// utils/logger.ts
export const logger = {
  info: (message: string) => console.log(`INFO: ${message}`),
  error: (message: string) => console.error(`ERROR: ${message}`),
};

// services/order.service.ts
import { logger } from "../utils/logger";

export class OrderService {
  async processOrder(orderId: string): Promise<void> {
    logger.info(`Processing order ${orderId}`);
    // Process order logic
    logger.info(`Order ${orderId} processed successfully`);
  }
}

// services/order.service.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { OrderService } from "./order.service";
import { logger } from "../utils/logger";

describe("OrderService", () => {
  let service: OrderService;
  let loggerSpy: any;

  beforeEach(() => {
    service = new OrderService();
    loggerSpy = vi.spyOn(logger, "info");
  });

  afterEach(() => {
    loggerSpy.mockRestore();
  });

  it("should log order processing", async () => {
    await service.processOrder("123");

    expect(loggerSpy).toHaveBeenCalledWith("Processing order 123");
    expect(loggerSpy).toHaveBeenCalledWith("Order 123 processed successfully");
    expect(loggerSpy).toHaveBeenCalledTimes(2);
  });
});

Integration Testing

Integration 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.

For full API integration test examples (supertest + PostgreSQL) and database repository integration tests, see references/advanced-testing-patterns.md.

Frontend Testing with Testing Library

Test 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.

For complete React component test examples (UserForm, hooks with renderHook/act), see references/advanced-testing-patterns.md.

Test Fixtures and Factories

Use @faker-js/faker to generate realistic test data factories. Factories accept optional overrides so tests can set only the fields they care about:

// tests/fixtures/user.fixture.ts
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(),
    createdAt: faker.date.past(),
    ...overrides,
  };
}

For snapshot testing, coverage configuration, test organization patterns, promise testing, and timer mocking, see references/advanced-testing-patterns.md.

Best Practices

  1. Follow AAA Pattern: Arrange, Act, Assert
  2. One assertion per test: Or logically related assertions
  3. Descriptive test names: Should describe what is being tested
  4. Use beforeEach/afterEach: For setup and teardown
  5. Mock external dependencies: Keep tests isolated
  6. Test edge cases: Not just happy paths
  7. Avoid implementation details: Test behavior, not implementation
  8. Use test factories: For consistent test data
  9. Keep tests fast: Mock slow operations
  10. Write tests first (TDD): When possible
  11. Maintain test coverage: Aim for 80%+ coverage
  12. Use TypeScript: For type-safe tests
  13. Test error handling: Not just success cases
  14. Use data-testid sparingly: Prefer semantic queries
  15. Clean up after tests: Prevent test pollution
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---
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}`);
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`.
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 
View all files on GitHub →

Details

Updated
Sep 21, 2026
License
MIT
Stars on the repo
★ 39,857
Checked
Source
wshobson/agents ↗
Creator
wshobson · 179 listings
View creator

Similar skills

See all →
Skill29 production-ready scripts for iOS app testing, buildingIos simulator skillconorluddy/ios-simulator-skill · by conorluddy29 production-ready scripts for iOS app testing, building, and automation. Provides semantic UI navigation, build automation, accessibility testing, and simulator lifecycle management. Optimized for AI agents with minimal token output.★ 1.3k GitHub starsSkillMoAI unified orchestrator for autonomous developmentmodu-ai/moai-adk · by modu-aiMoAI unified orchestrator for autonomous development. Routes natural language or subcommands (plan, run, sync, project, fix, loop, mx, feedback, review, clean, codemaps, gate, e2e, harness, goal, gtd, todo) to specialized agents.★ 1.2k GitHub starsSkillWrite Android unit, Compose UI, and Hilt-integrated testsAndroid Testing StandardsHoangNguyen0403/agent-skills-standard · by HoangNguyen0403Write Android unit, Compose UI, and Hilt-integrated tests. Use when designing test behavior with MockK or coroutine test utilities; defer database/WorkManager-specific recipes to the owning feature skill.★ 569 GitHub starsSkillImplementing any feature or bugfixTest-Driven Development (TDD)obra/superpowers · by obraUse when implementing any feature or bugfix, before writing implementation code★ 290k GitHub starsSkillToolkit for interacting with and testing local webWebapp testinganthropics/skills · by anthropicsToolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.★ 178k GitHub starsPromptGuide to fixing bugs using a test-first approachTest first bug fixing approachf/prompts.chat · by Ilker UlusoyGuide to fixing bugs using a test-first approach, ensuring code reliability through systematic testing and implementation.New listing

More from wshobson

SkillMaster C/.NET backend development patterns for buildingwshobson/agents · by wshobsonMaster C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.★ 39.9k GitHub starsSkillImplement WCAG 2.2 compliant interfaces with mobileAccessibility compliancewshobson/agents · by wshobsonImplement WCAG 2.2 compliant interfaces with mobile accessibility, inclusive design patterns, and assistive technology support. Use when auditing accessibility, implementing ARIA patterns, building for screen readers, or ensuring inclusive user experiences.★ 39.9k GitHub starsSkillMaster Material Design 3 and Jetpack Compose patternsAndroid Mobile Designwshobson/agents · by wshobsonMaster Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.★ 39.9k GitHub starsSkillMigrate from AngularJS to Angular using hybrid modeAngular migrationwshobson/agents · by wshobsonMigrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.★ 39.9k GitHub starsSkillUnderstand anti-reversing, obfuscationAnti reversing techniqueswshobson/agents · by wshobsonUnderstand anti-reversing, obfuscation, and protection techniques encountered during software analysis. Use this skill when analyzing malware evasion techniques, when implementing anti-debugging protections for CTF challenges, when reverse engineering packed binaries, or when building security research tools that need to detect virtualized environments.★ 39.9k GitHub starsSkillBuild production Apache Airflow DAGs with best practicesApache Airflow DAG Patternswshobson/agents · by wshobsonBuild production Apache Airflow DAGs with best practices for operators, sensors, testing, and deployment. Use when creating data pipelines, orchestrating workflows, or scheduling batch jobs.★ 39.9k GitHub stars