🎯

javascript-testing-patterns

🎯Skill

A practical guide to writing automated tests for JavaScript and TypeScript code with Jest, Vitest, and Testing Library. It shows ready-to-copy examples for checking functions, classes, and code that talks to outside services.

wshobson/agents16,622 Installs27,673Last Updated Jan 19, 2026

Installation

$npx skills add wshobson/agents --skill javascript-testing-patterns

Run it in a terminal; it asks which AI coding tools to install it for (Claude Code, Codex, Cursor and more).

Uses skills.sh Β· Node.js 22.20+

After installing

Ask for related work and the agent uses it on its own, or call it yourself.

Run it (Claude Code)/javascript-testing-patterns
Install by hand

Copy the whole skill folder from GitHub to the place below. Copying only SKILL.md can leave out files the skill uses.

Open the skill folder β†’
  • This project .claude/skills/javascript-testing-patterns/
  • All projects ~/.claude/skills/javascript-testing-patterns/

These paths are for Claude Code. For other tools, the command above puts it in the right place.

What this skill does

Overview

This skill helps you write automated tests for JavaScript and TypeScript projects. Automated tests are small programs that check your code still works after changes. It uses popular tools like Jest and Vitest and gives full examples you can adapt.

Key Features

  • Set up Jest or Vitest in a new project, including coverage goals that show how much code is tested.
  • Write tests for simple functions, classes, and code that waits for results.
  • Replace real services like email or web requests with fake versions, called mocks, during tests.
  • Design code so its parts are easy to swap out when testing.
  • Watch whether a function was called, such as checking that a log message was written.

Who is this for?

This is for JavaScript and TypeScript developers who want reliable tests or are starting test-first development. It also suits teams adding tests to frontend components or to automatic build checks.

Summarized by AI from this skill's SKILL.md. Suggest a better one

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)
  • Setting up continuous testing in CI/CD pipelines

Testing Frameworks

Jest - Full-Featured Testing Framework

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: "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("update", () => {
    it("should update existing user", () => {
      const user = { id: "1", name: "John", email: "john@example.com" };
      service.create(user);

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

      expect(updated.name).toBe("Jane");
      expect(updated.email).toBe("john@example.com");
    });

    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: "john@example.com" };

      (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: "john@example.com" };
      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(
      "test@example.com",
      "Test Subject",
      "<p>Test Body</p>",
    );

    expect(service["transporter"].sendMail).toHaveBeenCalledWith(
      expect.objectContaining({
        to: "test@example.com",
        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: "john@example.com" };
      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: "john@example.com" };
      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

More from this repository10

🎯
tailwind-design-system🎯Skill

Helps you build a consistent look for your app with Tailwind CSS v4. It sets up shared colors, sizes, dark mode and reusable components so every screen matches.

🎯
typescript-advanced-types🎯Skill

A guide to TypeScript's advanced type features, which describe the shape of your data so mistakes are caught before the code runs. It covers reusable types, types that change based on rules, and built-in helper types, with short examples.

🎯
nodejs-backend-patterns🎯Skill

Guidance for building reliable server-side apps with Node.js, using Express or Fastify. It covers login, error handling, databases, API design and a list of proven best practices.

πŸͺ
wshobson-agentsπŸͺMarketplace

Intelligent automation and multi-agent orchestration for Claude Code

πŸ”Œ
framework-migrationπŸ”ŒPlugin

Claude Code plugin for AI-powered framework migration workflows, coordinating specialized agents to plan and execute transitions between technology stacks as part of the wshobson/agents marketplace.

πŸ”Œ
payment-processingπŸ”ŒPlugin

A Claude Code plugin for payment system integration covering Stripe, PayPal, and billing platforms with checkout flows, subscription management, and webhook handling in the wshobson/agents ecosystem.

πŸ”Œ
developer-essentialsπŸ”ŒPlugin

A Claude Code plugin bundling core development tools and agents for everyday tasks like debugging and code generation, part of the wshobson/agents marketplace with 73 focused plugins.

πŸ”Œ
observability-monitoringπŸ”ŒPlugin

Claude Code plugin for observability and monitoring including logging, metrics, distributed tracing, and alerting, part of the wshobson/agents marketplace with 73 focused plugins.

πŸ”Œ
deployment-strategiesπŸ”ŒPlugin

A Claude Code plugin providing specialized AI agents for deployment strategy planning and execution, covering blue-green deployments, canary releases, rolling updates, and zero-downtime patterns.

πŸ”Œ
jvm-languagesπŸ”ŒPlugin

Claude Code plugin for JVM language development including Java, Kotlin, and Scala with specialized agents for idiomatic patterns, build tools, and runtime optimization in the wshobson/agents marketplace.