Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    wshobson

    javascript-testing-patterns

    wshobson/javascript-testing-patterns
    Coding
    28,185

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

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

    SKILL.md

    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
    Recommended Servers
    bugAgent
    bugAgent
    Browser tool
    Browser tool
    OpenZeppelin
    OpenZeppelin
    Repository
    wshobson/agents
    Files