Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Write effective JavaScript tests with Jest/Vitest, mocking strategies, async testing, and coverage best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/advanced-testing-patterns.md
1# Advanced JavaScript Testing Patterns23Advanced patterns for integration testing, frontend component testing, fixtures, snapshot testing, coverage, and common test utilities.45## Integration Testing67### Pattern 1: API Integration Tests89```typescript10// tests/integration/user.api.test.ts11import request from "supertest";12import { app } from "../../src/app";13import { pool } from "../../src/config/database";1415describe("User API Integration Tests", () => {16beforeAll(async () => {17// Setup test database18await pool.query("CREATE TABLE IF NOT EXISTS users (...)");19});2021afterAll(async () => {22// Cleanup23await pool.query("DROP TABLE IF EXISTS users");24await pool.end();25});2627beforeEach(async () => {28// Clear data before each test29await pool.query("TRUNCATE TABLE users CASCADE");30});3132describe("POST /api/users", () => {33it("should create a new user", async () => {34const userData = {35name: "John Doe",36email: "[email protected]",37password: "password123",38};3940const response = await request(app)41.post("/api/users")42.send(userData)43.expect(201);4445expect(response.body).toMatchObject({46name: userData.name,47email: userData.email,48});49expect(response.body).toHaveProperty("id");50expect(response.body).not.toHaveProperty("password");51});5253it("should return 400 if email is invalid", async () => {54const userData = {55name: "John Doe",56email: "invalid-email",57password: "password123",58};5960const response = await request(app)61.post("/api/users")62.send(userData)63.expect(400);6465expect(response.body).toHaveProperty("error");66});6768it("should return 409 if email already exists", async () => {69const userData = {70name: "John Doe",71email: "[email protected]",72password: "password123",73};7475await request(app).post("/api/users").send(userData);7677const response = await request(app)78.post("/api/users")79.send(userData)80.expect(409);8182expect(response.body.error).toContain("already exists");83});84});8586describe("GET /api/users/:id", () => {87it("should get user by id", async () => {88const createResponse = await request(app).post("/api/users").send({89name: "John Doe",90email: "[email protected]",91password: "password123",92});9394const userId = createResponse.body.id;9596const response = await request(app)97.get(`/api/users/${userId}`)98.expect(200);99100expect(response.body).toMatchObject({101id: userId,102name: "John Doe",103email: "[email protected]",104});105});106107it("should return 404 if user not found", async () => {108await request(app).get("/api/users/999").expect(404);109});110});111112describe("Authentication", () => {113it("should require authentication for protected routes", async () => {114await request(app).get("/api/users/me").expect(401);115});116117it("should allow access with valid token", async () => {118// Create user and login119await request(app).post("/api/users").send({120name: "John Doe",121email: "[email protected]",122password: "password123",123});124125const loginResponse = await request(app).post("/api/auth/login").send({126email: "[email protected]",127password: "password123",128});129130const token = loginResponse.body.token;131132const response = await request(app)133.get("/api/users/me")134.set("Authorization", `Bearer ${token}`)135.expect(200);136137expect(response.body.email).toBe("[email protected]");138});139});140});141```142143### Pattern 2: Database Integration Tests144145```typescript146// tests/integration/user.repository.test.ts147import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";148import { Pool } from "pg";149import { UserRepository } from "../../src/repositories/user.repository";150151describe("UserRepository Integration Tests", () => {152let pool: Pool;153let repository: UserRepository;154155beforeAll(async () => {156pool = new Pool({157host: "localhost",158port: 5432,159database: "test_db",160user: "test_user",161password: "test_password",162});163164repository = new UserRepository(pool);165166// Create tables167await pool.query(`168CREATE TABLE IF NOT EXISTS users (169id SERIAL PRIMARY KEY,170name VARCHAR(255) NOT NULL,171email VARCHAR(255) UNIQUE NOT NULL,172password VARCHAR(255) NOT NULL,173created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP174)175`);176});177178afterAll(async () => {179await pool.query("DROP TABLE IF EXISTS users");180await pool.end();181});182183beforeEach(async () => {184await pool.query("TRUNCATE TABLE users CASCADE");185});186187it("should create a user", async () => {188const user = await repository.create({189name: "John Doe",190email: "[email protected]",191password: "hashed_password",192});193194expect(user).toHaveProperty("id");195expect(user.name).toBe("John Doe");196expect(user.email).toBe("[email protected]");197});198199it("should find user by email", async () => {200await repository.create({201name: "John Doe",202email: "[email protected]",203password: "hashed_password",204});205206const user = await repository.findByEmail("[email protected]");207208expect(user).toBeTruthy();209expect(user?.name).toBe("John Doe");210});211212it("should return null if user not found", async () => {213const user = await repository.findByEmail("[email protected]");214expect(user).toBeNull();215});216});217```218219## Frontend Testing with Testing Library220221### Pattern 1: React Component Testing222223```typescript224// components/UserForm.tsx225import { useState } from 'react';226227interface Props {228onSubmit: (user: { name: string; email: string }) => void;229}230231export function UserForm({ onSubmit }: Props) {232const [name, setName] = useState('');233const [email, setEmail] = useState('');234235const handleSubmit = (e: React.FormEvent) => {236e.preventDefault();237onSubmit({ name, email });238};239240return (241<form onSubmit={handleSubmit}>242<input243type="text"244placeholder="Name"245value={name}246onChange={(e) => setName(e.target.value)}247data-testid="name-input"248/>249<input250type="email"251placeholder="Email"252value={email}253onChange={(e) => setEmail(e.target.value)}254data-testid="email-input"255/>256<button type="submit">Submit</button>257</form>258);259}260261// components/UserForm.test.tsx262import { render, screen, fireEvent } from '@testing-library/react';263import { describe, it, expect, vi } from 'vitest';264import { UserForm } from './UserForm';265266describe('UserForm', () => {267it('should render form inputs', () => {268render(<UserForm onSubmit={vi.fn()} />);269270expect(screen.getByPlaceholderText('Name')).toBeInTheDocument();271expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();272expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();273});274275it('should update input values', () => {276render(<UserForm onSubmit={vi.fn()} />);277278const nameInput = screen.getByTestId('name-input') as HTMLInputElement;279const emailInput = screen.getByTestId('email-input') as HTMLInputElement;280281fireEvent.change(nameInput, { target: { value: 'John Doe' } });282fireEvent.change(emailInput, { target: { value: '[email protected]' } });283284expect(nameInput.value).toBe('John Doe');285expect(emailInput.value).toBe('[email protected]');286});287288it('should call onSubmit with form data', () => {289const onSubmit = vi.fn();290render(<UserForm onSubmit={onSubmit} />);291292fireEvent.change(screen.getByTestId('name-input'), {293target: { value: 'John Doe' },294});295fireEvent.change(screen.getByTestId('email-input'), {296target: { value: '[email protected]' },297});298fireEvent.click(screen.getByRole('button', { name: 'Submit' }));299300expect(onSubmit).toHaveBeenCalledWith({301name: 'John Doe',302email: '[email protected]',303});304});305});306```307308### Pattern 2: Testing Hooks309310```typescript311// hooks/useCounter.ts312import { useState, useCallback } from "react";313314export function useCounter(initialValue = 0) {315const [count, setCount] = useState(initialValue);316317const increment = useCallback(() => setCount((c) => c + 1), []);318const decrement = useCallback(() => setCount((c) => c - 1), []);319const reset = useCallback(() => setCount(initialValue), [initialValue]);320321return { count, increment, decrement, reset };322}323324// hooks/useCounter.test.ts325import { renderHook, act } from "@testing-library/react";326import { describe, it, expect } from "vitest";327import { useCounter } from "./useCounter";328329describe("useCounter", () => {330it("should initialize with default value", () => {331const { result } = renderHook(() => useCounter());332expect(result.current.count).toBe(0);333});334335it("should initialize with custom value", () => {336const { result } = renderHook(() => useCounter(10));337expect(result.current.count).toBe(10);338});339340it("should increment count", () => {341const { result } = renderHook(() => useCounter());342343act(() => {344result.current.increment();345});346347expect(result.current.count).toBe(1);348});349350it("should decrement count", () => {351const { result } = renderHook(() => useCounter(5));352353act(() => {354result.current.decrement();355});356357expect(result.current.count).toBe(4);358});359360it("should reset to initial value", () => {361const { result } = renderHook(() => useCounter(10));362363act(() => {364result.current.increment();365result.current.increment();366});367368expect(result.current.count).toBe(12);369370act(() => {371result.current.reset();372});373374expect(result.current.count).toBe(10);375});376});377```378379## Test Fixtures and Factories380381```typescript382// tests/fixtures/user.fixture.ts383import { faker } from "@faker-js/faker";384385export function createUserFixture(overrides?: Partial<User>): User {386return {387id: faker.string.uuid(),388name: faker.person.fullName(),389email: faker.internet.email(),390createdAt: faker.date.past(),391...overrides,392};393}394395export function createUsersFixture(count: number): User[] {396return Array.from({ length: count }, () => createUserFixture());397}398399// Usage in tests400import {401createUserFixture,402createUsersFixture,403} from "../fixtures/user.fixture";404405describe("UserService", () => {406it("should process user", () => {407const user = createUserFixture({ name: "John Doe" });408// Use user in test409});410411it("should handle multiple users", () => {412const users = createUsersFixture(10);413// Use users in test414});415});416```417418## Snapshot Testing419420```typescript421// components/UserCard.test.tsx422import { render } from '@testing-library/react';423import { describe, it, expect } from 'vitest';424import { UserCard } from './UserCard';425426describe('UserCard', () => {427it('should match snapshot', () => {428const user = {429id: '1',430name: 'John Doe',431email: '[email protected]',432avatar: 'https://example.com/avatar.jpg',433};434435const { container } = render(<UserCard user={user} />);436437expect(container.firstChild).toMatchSnapshot();438});439440it('should match snapshot with loading state', () => {441const { container } = render(<UserCard loading />);442expect(container.firstChild).toMatchSnapshot();443});444});445```446447## Coverage Reports448449```typescript450// package.json451{452"scripts": {453"test": "vitest",454"test:coverage": "vitest --coverage",455"test:ui": "vitest --ui"456}457}458```459460## Common Patterns461462### Test Organization463464```typescript465describe("UserService", () => {466describe("createUser", () => {467it("should create user successfully", () => {});468it("should throw error if email exists", () => {});469it("should hash password", () => {});470});471472describe("updateUser", () => {473it("should update user", () => {});474it("should throw error if not found", () => {});475});476});477```478479### Testing Promises480481```typescript482// Using async/await483it("should fetch user", async () => {484const user = await service.fetchUser("1");485expect(user).toBeDefined();486});487488// Testing rejections489it("should throw error", async () => {490await expect(service.fetchUser("invalid")).rejects.toThrow("Not found");491});492```493494### Testing Timers495496```typescript497import { vi } from "vitest";498499it("should call function after delay", () => {500vi.useFakeTimers();501502const callback = vi.fn();503setTimeout(callback, 1000);504505expect(callback).not.toHaveBeenCalled();506507vi.advanceTimersByTime(1000);508509expect(callback).toHaveBeenCalled();510511vi.useRealTimers();512});513```514