[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F1EYVRVCNXBRFV316Q696A":3,"skill-same:01M6F1EYVRVCNXBRFV316Q696A":81},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":6,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6F1EYVRVCNXBRFV316Q696A","javascript-testing-patterns",null,"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\u002FTypeScript tests, setting up test infrastructure, or implementing TDD\u002FBDD workflows.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fjavascript-testing-patterns\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"testing","javascript","patterns","javascript testing patterns","testing patterns","unit","end","strategies","jest","up","bdd","vitest","library","testing library","development","infrastructure","integration tests","workflows","typescript","writing","2026-11-10 10:12:30",false,"---\nname: javascript-testing-patterns\ndescription: 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\u002FTypeScript tests, setting up test infrastructure, or implementing TDD\u002FBDD workflows.\n---\n\n# JavaScript Testing Patterns\n\nComprehensive guide for implementing robust testing strategies in JavaScript\u002FTypeScript applications using modern testing frameworks and best practices.\n\n## When to Use This Skill\n\n- Setting up test infrastructure for new projects\n- Writing unit tests for functions and classes\n- Creating integration tests for APIs and services\n- Implementing end-to-end tests for user flows\n- Mocking external dependencies and APIs\n- Testing React, Vue, or other frontend components\n- Implementing test-driven development (TDD)\n- Setting up continuous testing in CI\u002FCD pipelines\n\n## Testing Frameworks\n\n### Jest - Full-Featured Testing Framework\n\n**Setup:**\n```typescript\n\u002F\u002F jest.config.ts\nimport type { Config } from 'jest';\n\nconst config: Config = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  roots: ['\u003CrootDir>\u002Fsrc'],\n  testMatch: ['**\u002F__tests__\u002F**\u002F*.ts', '**\u002F?(*.)+(spec|test).ts'],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.ts',\n    '!src\u002F**\u002F*.d.ts',\n    '!src\u002F**\u002F*.interface.ts',\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80,\n    },\n  },\n  setupFilesAfterEnv: ['\u003CrootDir>\u002Fsrc\u002Ftest\u002Fsetup.ts'],\n};\n\nexport default config;\n```\n\n### Vitest - Fast, Vite-Native Testing\n\n**Setup:**\n```typescript\n\u002F\u002F vitest.config.ts\nimport { defineConfig } from 'vitest\u002Fconfig';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'node',\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'json', 'html'],\n      exclude: ['**\u002F*.d.ts', '**\u002F*.config.ts', '**\u002Fdist\u002F**'],\n    },\n    setupFiles: ['.\u002Fsrc\u002Ftest\u002Fsetup.ts'],\n  },\n});\n```\n\n## Unit Testing Patterns\n\n### Pattern 1: Testing Pure Functions\n\n```typescript\n\u002F\u002F utils\u002Fcalculator.ts\nexport function add(a: number, b: number): number {\n  return a + b;\n}\n\nexport function divide(a: number, b: number): number {\n  if (b === 0) {\n    throw new Error('Division by zero');\n  }\n  return a \u002F b;\n}\n\n\u002F\u002F utils\u002Fcalculator.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { add, divide } from '.\u002Fcalculator';\n\ndescribe('Calculator', () => {\n  describe('add', () => {\n    it('should add two positive numbers', () => {\n      expect(add(2, 3)).toBe(5);\n    });\n\n    it('should add negative numbers', () => {\n      expect(add(-2, -3)).toBe(-5);\n    });\n\n    it('should handle zero', () => {\n      expect(add(0, 5)).toBe(5);\n      expect(add(5, 0)).toBe(5);\n    });\n  });\n\n  describe('divide', () => {\n    it('should divide two numbers', () => {\n      expect(divide(10, 2)).toBe(5);\n    });\n\n    it('should handle decimal results', () => {\n      expect(divide(5, 2)).toBe(2.5);\n    });\n\n    it('should throw error when dividing by zero', () => {\n      expect(() => divide(10, 0)).toThrow('Division by zero');\n    });\n  });\n});\n```\n\n### Pattern 2: Testing Classes\n\n```typescript\n\u002F\u002F services\u002Fuser.service.ts\nexport class UserService {\n  private users: Map\u003Cstring, User> = new Map();\n\n  create(user: User): User {\n    if (this.users.has(user.id)) {\n      throw new Error('User already exists');\n    }\n    this.users.set(user.id, user);\n    return user;\n  }\n\n  findById(id: string): User | undefined {\n    return this.users.get(id);\n  }\n\n  update(id: string, updates: Partial\u003CUser>): User {\n    const user = this.users.get(id);\n    if (!user) {\n      throw new Error('User not found');\n    }\n    const updated = { ...user, ...updates };\n    this.users.set(id, updated);\n    return updated;\n  }\n\n  delete(id: string): boolean {\n    return this.users.delete(id);\n  }\n}\n\n\u002F\u002F services\u002Fuser.service.test.ts\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { UserService } from '.\u002Fuser.service';\n\ndescribe('UserService', () => {\n  let service: UserService;\n\n  beforeEach(() => {\n    service = new UserService();\n  });\n\n  describe('create', () => {\n    it('should create a new user', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      const created = service.create(user);\n\n      expect(created).toEqual(user);\n      expect(service.findById('1')).toEqual(user);\n    });\n\n    it('should throw error if user already exists', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      service.create(user);\n\n      expect(() => service.create(user)).toThrow('User already exists');\n    });\n  });\n\n  describe('update', () => {\n    it('should update existing user', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      service.create(user);\n\n      const updated = service.update('1', { name: 'Jane' });\n\n      expect(updated.name).toBe('Jane');\n      expect(updated.email).toBe('john@example.com');\n    });\n\n    it('should throw error if user not found', () => {\n      expect(() => service.update('999', { name: 'Jane' }))\n        .toThrow('User not found');\n    });\n  });\n});\n```\n\n### Pattern 3: Testing Async Functions\n\n```typescript\n\u002F\u002F services\u002Fapi.service.ts\nexport class ApiService {\n  async fetchUser(id: string): Promise\u003CUser> {\n    const response = await fetch(`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${id}`);\n    if (!response.ok) {\n      throw new Error('User not found');\n    }\n    return response.json();\n  }\n\n  async createUser(user: CreateUserDTO): Promise\u003CUser> {\n    const response = await fetch('https:\u002F\u002Fapi.example.com\u002Fusers', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(user),\n    });\n    return response.json();\n  }\n}\n\n\u002F\u002F services\u002Fapi.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { ApiService } from '.\u002Fapi.service';\n\n\u002F\u002F Mock fetch globally\nglobal.fetch = vi.fn();\n\ndescribe('ApiService', () => {\n  let service: ApiService;\n\n  beforeEach(() => {\n    service = new ApiService();\n    vi.clearAllMocks();\n  });\n\n  describe('fetchUser', () => {\n    it('should fetch user successfully', async () => {\n      const mockUser = { id: '1', name: 'John', email: 'john@example.com' };\n\n      (fetch as any).mockResolvedValueOnce({\n        ok: true,\n        json: async () => mockUser,\n      });\n\n      const user = await service.fetchUser('1');\n\n      expect(user).toEqual(mockUser);\n      expect(fetch).toHaveBeenCalledWith('https:\u002F\u002Fapi.example.com\u002Fusers\u002F1');\n    });\n\n    it('should throw error if user not found', async () => {\n      (fetch as any).mockResolvedValueOnce({\n        ok: false,\n      });\n\n      await expect(service.fetchUser('999')).rejects.toThrow('User not found');\n    });\n  });\n\n  describe('createUser', () => {\n    it('should create user successfully', async () => {\n      const newUser = { name: 'John', email: 'john@example.com' };\n      const createdUser = { id: '1', ...newUser };\n\n      (fetch as any).mockResolvedValueOnce({\n        ok: true,\n        json: async () => createdUser,\n      });\n\n      const user = await service.createUser(newUser);\n\n      expect(user).toEqual(createdUser);\n      expect(fetch).toHaveBeenCalledWith(\n        'https:\u002F\u002Fapi.example.com\u002Fusers',\n        expect.objectContaining({\n          method: 'POST',\n          body: JSON.stringify(newUser),\n        })\n      );\n    });\n  });\n});\n```\n\n## Mocking Patterns\n\n### Pattern 1: Mocking Modules\n\n```typescript\n\u002F\u002F services\u002Femail.service.ts\nimport nodemailer from 'nodemailer';\n\nexport class EmailService {\n  private transporter = nodemailer.createTransport({\n    host: process.env.SMTP_HOST,\n    port: 587,\n    auth: {\n      user: process.env.SMTP_USER,\n      pass: process.env.SMTP_PASS,\n    },\n  });\n\n  async sendEmail(to: string, subject: string, html: string) {\n    await this.transporter.sendMail({\n      from: process.env.EMAIL_FROM,\n      to,\n      subject,\n      html,\n    });\n  }\n}\n\n\u002F\u002F services\u002Femail.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EmailService } from '.\u002Femail.service';\n\nvi.mock('nodemailer', () => ({\n  default: {\n    createTransport: vi.fn(() => ({\n      sendMail: vi.fn().mockResolvedValue({ messageId: '123' }),\n    })),\n  },\n}));\n\ndescribe('EmailService', () => {\n  let service: EmailService;\n\n  beforeEach(() => {\n    service = new EmailService();\n  });\n\n  it('should send email successfully', async () => {\n    await service.sendEmail(\n      'test@example.com',\n      'Test Subject',\n      '\u003Cp>Test Body\u003C\u002Fp>'\n    );\n\n    expect(service['transporter'].sendMail).toHaveBeenCalledWith(\n      expect.objectContaining({\n        to: 'test@example.com',\n        subject: 'Test Subject',\n      })\n    );\n  });\n});\n```\n\n### Pattern 2: Dependency Injection for Testing\n\n```typescript\n\u002F\u002F services\u002Fuser.service.ts\nexport interface IUserRepository {\n  findById(id: string): Promise\u003CUser | null>;\n  create(user: User): Promise\u003CUser>;\n}\n\nexport class UserService {\n  constructor(private userRepository: IUserRepository) {}\n\n  async getUser(id: string): Promise\u003CUser> {\n    const user = await this.userRepository.findById(id);\n    if (!user) {\n      throw new Error('User not found');\n    }\n    return user;\n  }\n\n  async createUser(userData: CreateUserDTO): Promise\u003CUser> {\n    \u002F\u002F Business logic here\n    const user = { id: generateId(), ...userData };\n    return this.userRepository.create(user);\n  }\n}\n\n\u002F\u002F services\u002Fuser.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { UserService, IUserRepository } from '.\u002Fuser.service';\n\ndescribe('UserService', () => {\n  let service: UserService;\n  let mockRepository: IUserRepository;\n\n  beforeEach(() => {\n    mockRepository = {\n      findById: vi.fn(),\n      create: vi.fn(),\n    };\n    service = new UserService(mockRepository);\n  });\n\n  describe('getUser', () => {\n    it('should return user if found', async () => {\n      const mockUser = { id: '1', name: 'John', email: 'john@example.com' };\n      vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);\n\n      const user = await service.getUser('1');\n\n      expect(user).toEqual(mockUser);\n      expect(mockRepository.findById).toHaveBeenCalledWith('1');\n    });\n\n    it('should throw error if user not found', async () => {\n      vi.mocked(mockRepository.findById).mockResolvedValue(null);\n\n      await expect(service.getUser('999')).rejects.toThrow('User not found');\n    });\n  });\n\n  describe('createUser', () => {\n    it('should create user successfully', async () => {\n      const userData = { name: 'John', email: 'john@example.com' };\n      const createdUser = { id: '1', ...userData };\n\n      vi.mocked(mockRepository.create).mockResolvedValue(createdUser);\n\n      const user = await service.createUser(userData);\n\n      expect(user).toEqual(createdUser);\n      expect(mockRepository.create).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n### Pattern 3: Spying on Functions\n\n```typescript\n\u002F\u002F utils\u002Flogger.ts\nexport const logger = {\n  info: (message: string) => console.log(`INFO: ${message}`),\n  error: (message: string) => console.error(`ERROR: ${message}`),\n};\n\n\u002F\u002F services\u002Forder.service.ts\nimport { logger } from '..\u002Futils\u002Flogger';\n\nexport class OrderService {\n  async processOrder(orderId: string): Promise\u003Cvoid> {\n    logger.info(`Processing order ${orderId}`);\n    \u002F\u002F Process order logic\n    logger.info(`Order ${orderId} processed successfully`);\n  }\n}\n\n\u002F\u002F services\u002Forder.service.test.ts\nimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { OrderService } from '.\u002Forder.service';\nimport { logger } from '..\u002Futils\u002Flogger';\n\ndescribe('OrderService', () => {\n  let service: OrderService;\n  let loggerSpy: any;\n\n  beforeEach(() => {\n    service = new OrderService();\n    loggerSpy = vi.spyOn(logger, 'info');\n  });\n\n  afterEach(() => {\n    loggerSpy.mockRestore();\n  });\n\n  it('should log order processing', async () => {\n    await service.processOrder('123');\n\n    expect(loggerSpy).toHaveBeenCalledWith('Processing order 123');\n    expect(loggerSpy).toHaveBeenCalledWith('Order 123 processed successfully');\n    expect(loggerSpy).toHaveBeenCalledTimes(2);\n  });\n});\n```\n\n## Integration Testing\n\n### Pattern 1: API Integration Tests\n\n```typescript\n\u002F\u002F tests\u002Fintegration\u002Fuser.api.test.ts\nimport request from 'supertest';\nimport { app } from '..\u002F..\u002Fsrc\u002Fapp';\nimport { pool } from '..\u002F..\u002Fsrc\u002Fconfig\u002Fdatabase';\n\ndescribe('User API Integration Tests', () => {\n  beforeAll(async () => {\n    \u002F\u002F Setup test database\n    await pool.query('CREATE TABLE IF NOT EXISTS users (...)');\n  });\n\n  afterAll(async () => {\n    \u002F\u002F Cleanup\n    await pool.query('DROP TABLE IF EXISTS users');\n    await pool.end();\n  });\n\n  beforeEach(async () => {\n    \u002F\u002F Clear data before each test\n    await pool.query('TRUNCATE TABLE users CASCADE');\n  });\n\n  describe('POST \u002Fapi\u002Fusers', () => {\n    it('should create a new user', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'john@example.com',\n        password: 'password123',\n      };\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(201);\n\n      expect(response.body).toMatchObject({\n        name: userData.name,\n        email: userData.email,\n      });\n      expect(response.body).toHaveProperty('id');\n      expect(response.body).not.toHaveProperty('password');\n    });\n\n    it('should return 400 if email is invalid', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'invalid-email',\n        password: 'password123',\n      };\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(400);\n\n      expect(response.body).toHaveProperty('error');\n    });\n\n    it('should return 409 if email already exists', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'john@example.com',\n        password: 'password123',\n      };\n\n      await request(app).post('\u002Fapi\u002Fusers').send(userData);\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(409);\n\n      expect(response.body.error).toContain('already exists');\n    });\n  });\n\n  describe('GET \u002Fapi\u002Fusers\u002F:id', () => {\n    it('should get user by id', async () => {\n      const createResponse = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send({\n          name: 'John Doe',\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const userId = createResponse.body.id;\n\n      const response = await request(app)\n        .get(`\u002Fapi\u002Fusers\u002F${userId}`)\n        .expect(200);\n\n      expect(response.body).toMatchObject({\n        id: userId,\n        name: 'John Doe',\n        email: 'john@example.com',\n      });\n    });\n\n    it('should return 404 if user not found', async () => {\n      await request(app)\n        .get('\u002Fapi\u002Fusers\u002F999')\n        .expect(404);\n    });\n  });\n\n  describe('Authentication', () => {\n    it('should require authentication for protected routes', async () => {\n      await request(app)\n        .get('\u002Fapi\u002Fusers\u002Fme')\n        .expect(401);\n    });\n\n    it('should allow access with valid token', async () => {\n      \u002F\u002F Create user and login\n      await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send({\n          name: 'John Doe',\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const loginResponse = await request(app)\n        .post('\u002Fapi\u002Fauth\u002Flogin')\n        .send({\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const token = loginResponse.body.token;\n\n      const response = await request(app)\n        .get('\u002Fapi\u002Fusers\u002Fme')\n        .set('Authorization', `Bearer ${token}`)\n        .expect(200);\n\n      expect(response.body.email).toBe('john@example.com');\n    });\n  });\n});\n```\n\n### Pattern 2: Database Integration Tests\n\n```typescript\n\u002F\u002F tests\u002Fintegration\u002Fuser.repository.test.ts\nimport { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';\nimport { Pool } from 'pg';\nimport { UserRepository } from '..\u002F..\u002Fsrc\u002Frepositories\u002Fuser.repository';\n\ndescribe('UserRepository Integration Tests', () => {\n  let pool: Pool;\n  let repository: UserRepository;\n\n  beforeAll(async () => {\n    pool = new Pool({\n      host: 'localhost',\n      port: 5432,\n      database: 'test_db',\n      user: 'test_user',\n      password: 'test_password',\n    });\n\n    repository = new UserRepository(pool);\n\n    \u002F\u002F Create tables\n    await pool.query(`\n      CREATE TABLE IF NOT EXISTS users (\n        id SERIAL PRIMARY KEY,\n        name VARCHAR(255) NOT NULL,\n        email VARCHAR(255) UNIQUE NOT NULL,\n        password VARCHAR(255) NOT NULL,\n        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n      )\n    `);\n  });\n\n  afterAll(async () => {\n    await pool.query('DROP TABLE IF EXISTS users');\n    await pool.end();\n  });\n\n  beforeEach(async () => {\n    await pool.query('TRUNCATE TABLE users CASCADE');\n  });\n\n  it('should create a user', async () => {\n    const user = await repository.create({\n      name: 'John Doe',\n      email: 'john@example.com',\n      password: 'hashed_password',\n    });\n\n    expect(user).toHaveProperty('id');\n    expect(user.name).toBe('John Doe');\n    expect(user.email).toBe('john@example.com');\n  });\n\n  it('should find user by email', async () => {\n    await repository.create({\n      name: 'John Doe',\n      email: 'john@example.com',\n      password: 'hashed_password',\n    });\n\n    const user = await repository.findByEmail('john@example.com');\n\n    expect(user).toBeTruthy();\n    expect(user?.name).toBe('John Doe');\n  });\n\n  it('should return null if user not found', async () => {\n    const user = await repository.findByEmail('nonexistent@example.com');\n    expect(user).toBeNull();\n  });\n});\n```\n\n## Frontend Testing with Testing Library\n\n### Pattern 1: React Component Testing\n\n```typescript\n\u002F\u002F components\u002FUserForm.tsx\nimport { useState } from 'react';\n\ninterface Props {\n  onSubmit: (user: { name: string; email: string }) => void;\n}\n\nexport function UserForm({ onSubmit }: Props) {\n  const [name, setName] = useState('');\n  const [email, setEmail] = useState('');\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    onSubmit({ name, email });\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput\n        type=\"text\"\n        placeholder=\"Name\"\n        value={name}\n        onChange={(e) => setName(e.target.value)}\n        data-testid=\"name-input\"\n      \u002F>\n      \u003Cinput\n        type=\"email\"\n        placeholder=\"Email\"\n        value={email}\n        onChange={(e) => setEmail(e.target.value)}\n        data-testid=\"email-input\"\n      \u002F>\n      \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n\n\u002F\u002F components\u002FUserForm.test.tsx\nimport { render, screen, fireEvent } from '@testing-library\u002Freact';\nimport { describe, it, expect, vi } from 'vitest';\nimport { UserForm } from '.\u002FUserForm';\n\ndescribe('UserForm', () => {\n  it('should render form inputs', () => {\n    render(\u003CUserForm onSubmit={vi.fn()} \u002F>);\n\n    expect(screen.getByPlaceholderText('Name')).toBeInTheDocument();\n    expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();\n    expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();\n  });\n\n  it('should update input values', () => {\n    render(\u003CUserForm onSubmit={vi.fn()} \u002F>);\n\n    const nameInput = screen.getByTestId('name-input') as HTMLInputElement;\n    const emailInput = screen.getByTestId('email-input') as HTMLInputElement;\n\n    fireEvent.change(nameInput, { target: { value: 'John Doe' } });\n    fireEvent.change(emailInput, { target: { value: 'john@example.com' } });\n\n    expect(nameInput.value).toBe('John Doe');\n    expect(emailInput.value).toBe('john@example.com');\n  });\n\n  it('should call onSubmit with form data', () => {\n    const onSubmit = vi.fn();\n    render(\u003CUserForm onSubmit={onSubmit} \u002F>);\n\n    fireEvent.change(screen.getByTestId('name-input'), {\n      target: { value: 'John Doe' },\n    });\n    fireEvent.change(screen.getByTestId('email-input'), {\n      target: { value: 'john@example.com' },\n    });\n    fireEvent.click(screen.getByRole('button', { name: 'Submit' }));\n\n    expect(onSubmit).toHaveBeenCalledWith({\n      name: 'John Doe',\n      email: 'john@example.com',\n    });\n  });\n});\n```\n\n### Pattern 2: Testing Hooks\n\n```typescript\n\u002F\u002F hooks\u002FuseCounter.ts\nimport { useState, useCallback } from 'react';\n\nexport function useCounter(initialValue = 0) {\n  const [count, setCount] = useState(initialValue);\n\n  const increment = useCallback(() => setCount((c) => c + 1), []);\n  const decrement = useCallback(() => setCount((c) => c - 1), []);\n  const reset = useCallback(() => setCount(initialValue), [initialValue]);\n\n  return { count, increment, decrement, reset };\n}\n\n\u002F\u002F hooks\u002FuseCounter.test.ts\nimport { renderHook, act } from '@testing-library\u002Freact';\nimport { describe, it, expect } from 'vitest';\nimport { useCounter } from '.\u002FuseCounter';\n\ndescribe('useCounter', () => {\n  it('should initialize with default value', () => {\n    const { result } = renderHook(() => useCounter());\n    expect(result.current.count).toBe(0);\n  });\n\n  it('should initialize with custom value', () => {\n    const { result } = renderHook(() => useCounter(10));\n    expect(result.current.count).toBe(10);\n  });\n\n  it('should increment count', () => {\n    const { result } = renderHook(() => useCounter());\n\n    act(() => {\n      result.current.increment();\n    });\n\n    expect(result.current.count).toBe(1);\n  });\n\n  it('should decrement count', () => {\n    const { result } = renderHook(() => useCounter(5));\n\n    act(() => {\n      result.current.decrement();\n    });\n\n    expect(result.current.count).toBe(4);\n  });\n\n  it('should reset to initial value', () => {\n    const { result } = renderHook(() => useCounter(10));\n\n    act(() => {\n      result.current.increment();\n      result.current.increment();\n    });\n\n    expect(result.current.count).toBe(12);\n\n    act(() => {\n      result.current.reset();\n    });\n\n    expect(result.current.count).toBe(10);\n  });\n});\n```\n\n## Test Fixtures and Factories\n\n```typescript\n\u002F\u002F tests\u002Ffixtures\u002Fuser.fixture.ts\nimport { faker } from '@faker-js\u002Ffaker';\n\nexport function createUserFixture(overrides?: Partial\u003CUser>): User {\n  return {\n    id: faker.string.uuid(),\n    name: faker.person.fullName(),\n    email: faker.internet.email(),\n    createdAt: faker.date.past(),\n    ...overrides,\n  };\n}\n\nexport function createUsersFixture(count: number): User[] {\n  return Array.from({ length: count }, () => createUserFixture());\n}\n\n\u002F\u002F Usage in tests\nimport { createUserFixture, createUsersFixture } from '..\u002Ffixtures\u002Fuser.fixture';\n\ndescribe('UserService', () => {\n  it('should process user', () => {\n    const user = createUserFixture({ name: 'John Doe' });\n    \u002F\u002F Use user in test\n  });\n\n  it('should handle multiple users', () => {\n    const users = createUsersFixture(10);\n    \u002F\u002F Use users in test\n  });\n});\n```\n\n## Snapshot Testing\n\n```typescript\n\u002F\u002F components\u002FUserCard.test.tsx\nimport { render } from '@testing-library\u002Freact';\nimport { describe, it, expect } from 'vitest';\nimport { UserCard } from '.\u002FUserCard';\n\ndescribe('UserCard', () => {\n  it('should match snapshot', () => {\n    const user = {\n      id: '1',\n      name: 'John Doe',\n      email: 'john@example.com',\n      avatar: 'https:\u002F\u002Fexample.com\u002Favatar.jpg',\n    };\n\n    const { container } = render(\u003CUserCard user={user} \u002F>);\n\n    expect(container.firstChild).toMatchSnapshot();\n  });\n\n  it('should match snapshot with loading state', () => {\n    const { container } = render(\u003CUserCard loading \u002F>);\n    expect(container.firstChild).toMatchSnapshot();\n  });\n});\n```\n\n## Coverage Reports\n\n```typescript\n\u002F\u002F package.json\n{\n  \"scripts\": {\n    \"test\": \"vitest\",\n    \"test:coverage\": \"vitest --coverage\",\n    \"test:ui\": \"vitest --ui\"\n  }\n}\n```\n\n## Best Practices\n\n1. **Follow AAA Pattern**: Arrange, Act, Assert\n2. **One assertion per test**: Or logically related assertions\n3. **Descriptive test names**: Should describe what is being tested\n4. **Use beforeEach\u002FafterEach**: For setup and teardown\n5. **Mock external dependencies**: Keep tests isolated\n6. **Test edge cases**: Not just happy paths\n7. **Avoid implementation details**: Test behavior, not implementation\n8. **Use test factories**: For consistent test data\n9. **Keep tests fast**: Mock slow operations\n10. **Write tests first (TDD)**: When possible\n11. **Maintain test coverage**: Aim for 80%+ coverage\n12. **Use TypeScript**: For type-safe tests\n13. **Test error handling**: Not just success cases\n14. **Use data-testid sparingly**: Prefer semantic queries\n15. **Clean up after tests**: Prevent test pollution\n\n## Common Patterns\n\n### Test Organization\n\n```typescript\ndescribe('UserService', () => {\n  describe('createUser', () => {\n    it('should create user successfully', () => {});\n    it('should throw error if email exists', () => {});\n    it('should hash password', () => {});\n  });\n\n  describe('updateUser', () => {\n    it('should update user', () => {});\n    it('should throw error if not found', () => {});\n  });\n});\n```\n\n### Testing Promises\n\n```typescript\n\u002F\u002F Using async\u002Fawait\nit('should fetch user', async () => {\n  const user = await service.fetchUser('1');\n  expect(user).toBeDefined();\n});\n\n\u002F\u002F Testing rejections\nit('should throw error', async () => {\n  await expect(service.fetchUser('invalid')).rejects.toThrow('Not found');\n});\n```\n\n### Testing Timers\n\n```typescript\nimport { vi } from 'vitest';\n\nit('should call function after delay', () => {\n  vi.useFakeTimers();\n\n  const callback = vi.fn();\n  setTimeout(callback, 1000);\n\n  expect(callback).not.toHaveBeenCalled();\n\n  vi.advanceTimersByTime(1000);\n\n  expect(callback).toHaveBeenCalled();\n\n  vi.useRealTimers();\n});\n```\n\n## Resources\n\n- **Jest Documentation**: https:\u002F\u002Fjestjs.io\u002F\n- **Vitest Documentation**: https:\u002F\u002Fvitest.dev\u002F\n- **Testing Library**: https:\u002F\u002Ftesting-library.com\u002F\n- **Kent C. Dodds Testing Blog**: https:\u002F\u002Fkentcdodds.com\u002Fblog\u002F\n","# JavaScript Testing Patterns\n\nComprehensive guide for implementing robust testing strategies in JavaScript\u002FTypeScript applications using modern testing frameworks and best practices.\n\n## When to Use This Skill\n\n- Setting up test infrastructure for new projects\n- Writing unit tests for functions and classes\n- Creating integration tests for APIs and services\n- Implementing end-to-end tests for user flows\n- Mocking external dependencies and APIs\n- Testing React, Vue, or other frontend components\n- Implementing test-driven development (TDD)\n- Setting up continuous testing in CI\u002FCD pipelines\n\n## Testing Frameworks\n\n### Jest - Full-Featured Testing Framework\n\n**Setup:**\n```typescript\n\u002F\u002F jest.config.ts\nimport type { Config } from 'jest';\n\nconst config: Config = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  roots: ['\u003CrootDir>\u002Fsrc'],\n  testMatch: ['**\u002F__tests__\u002F**\u002F*.ts', '**\u002F?(*.)+(spec|test).ts'],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.ts',\n    '!src\u002F**\u002F*.d.ts',\n    '!src\u002F**\u002F*.interface.ts',\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80,\n    },\n  },\n  setupFilesAfterEnv: ['\u003CrootDir>\u002Fsrc\u002Ftest\u002Fsetup.ts'],\n};\n\nexport default config;\n```\n\n### Vitest - Fast, Vite-Native Testing\n\n**Setup:**\n```typescript\n\u002F\u002F vitest.config.ts\nimport { defineConfig } from 'vitest\u002Fconfig';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'node',\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'json', 'html'],\n      exclude: ['**\u002F*.d.ts', '**\u002F*.config.ts', '**\u002Fdist\u002F**'],\n    },\n    setupFiles: ['.\u002Fsrc\u002Ftest\u002Fsetup.ts'],\n  },\n});\n```\n\n## Unit Testing Patterns\n\n### Pattern 1: Testing Pure Functions\n\n```typescript\n\u002F\u002F utils\u002Fcalculator.ts\nexport function add(a: number, b: number): number {\n  return a + b;\n}\n\nexport function divide(a: number, b: number): number {\n  if (b === 0) {\n    throw new Error('Division by zero');\n  }\n  return a \u002F b;\n}\n\n\u002F\u002F utils\u002Fcalculator.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { add, divide } from '.\u002Fcalculator';\n\ndescribe('Calculator', () => {\n  describe('add', () => {\n    it('should add two positive numbers', () => {\n      expect(add(2, 3)).toBe(5);\n    });\n\n    it('should add negative numbers', () => {\n      expect(add(-2, -3)).toBe(-5);\n    });\n\n    it('should handle zero', () => {\n      expect(add(0, 5)).toBe(5);\n      expect(add(5, 0)).toBe(5);\n    });\n  });\n\n  describe('divide', () => {\n    it('should divide two numbers', () => {\n      expect(divide(10, 2)).toBe(5);\n    });\n\n    it('should handle decimal results', () => {\n      expect(divide(5, 2)).toBe(2.5);\n    });\n\n    it('should throw error when dividing by zero', () => {\n      expect(() => divide(10, 0)).toThrow('Division by zero');\n    });\n  });\n});\n```\n\n### Pattern 2: Testing Classes\n\n```typescript\n\u002F\u002F services\u002Fuser.service.ts\nexport class UserService {\n  private users: Map\u003Cstring, User> = new Map();\n\n  create(user: User): User {\n    if (this.users.has(user.id)) {\n      throw new Error('User already exists');\n    }\n    this.users.set(user.id, user);\n    return user;\n  }\n\n  findById(id: string): User | undefined {\n    return this.users.get(id);\n  }\n\n  update(id: string, updates: Partial\u003CUser>): User {\n    const user = this.users.get(id);\n    if (!user) {\n      throw new Error('User not found');\n    }\n    const updated = { ...user, ...updates };\n    this.users.set(id, updated);\n    return updated;\n  }\n\n  delete(id: string): boolean {\n    return this.users.delete(id);\n  }\n}\n\n\u002F\u002F services\u002Fuser.service.test.ts\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { UserService } from '.\u002Fuser.service';\n\ndescribe('UserService', () => {\n  let service: UserService;\n\n  beforeEach(() => {\n    service = new UserService();\n  });\n\n  describe('create', () => {\n    it('should create a new user', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      const created = service.create(user);\n\n      expect(created).toEqual(user);\n      expect(service.findById('1')).toEqual(user);\n    });\n\n    it('should throw error if user already exists', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      service.create(user);\n\n      expect(() => service.create(user)).toThrow('User already exists');\n    });\n  });\n\n  describe('update', () => {\n    it('should update existing user', () => {\n      const user = { id: '1', name: 'John', email: 'john@example.com' };\n      service.create(user);\n\n      const updated = service.update('1', { name: 'Jane' });\n\n      expect(updated.name).toBe('Jane');\n      expect(updated.email).toBe('john@example.com');\n    });\n\n    it('should throw error if user not found', () => {\n      expect(() => service.update('999', { name: 'Jane' }))\n        .toThrow('User not found');\n    });\n  });\n});\n```\n\n### Pattern 3: Testing Async Functions\n\n```typescript\n\u002F\u002F services\u002Fapi.service.ts\nexport class ApiService {\n  async fetchUser(id: string): Promise\u003CUser> {\n    const response = await fetch(`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${id}`);\n    if (!response.ok) {\n      throw new Error('User not found');\n    }\n    return response.json();\n  }\n\n  async createUser(user: CreateUserDTO): Promise\u003CUser> {\n    const response = await fetch('https:\u002F\u002Fapi.example.com\u002Fusers', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(user),\n    });\n    return response.json();\n  }\n}\n\n\u002F\u002F services\u002Fapi.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { ApiService } from '.\u002Fapi.service';\n\n\u002F\u002F Mock fetch globally\nglobal.fetch = vi.fn();\n\ndescribe('ApiService', () => {\n  let service: ApiService;\n\n  beforeEach(() => {\n    service = new ApiService();\n    vi.clearAllMocks();\n  });\n\n  describe('fetchUser', () => {\n    it('should fetch user successfully', async () => {\n      const mockUser = { id: '1', name: 'John', email: 'john@example.com' };\n\n      (fetch as any).mockResolvedValueOnce({\n        ok: true,\n        json: async () => mockUser,\n      });\n\n      const user = await service.fetchUser('1');\n\n      expect(user).toEqual(mockUser);\n      expect(fetch).toHaveBeenCalledWith('https:\u002F\u002Fapi.example.com\u002Fusers\u002F1');\n    });\n\n    it('should throw error if user not found', async () => {\n      (fetch as any).mockResolvedValueOnce({\n        ok: false,\n      });\n\n      await expect(service.fetchUser('999')).rejects.toThrow('User not found');\n    });\n  });\n\n  describe('createUser', () => {\n    it('should create user successfully', async () => {\n      const newUser = { name: 'John', email: 'john@example.com' };\n      const createdUser = { id: '1', ...newUser };\n\n      (fetch as any).mockResolvedValueOnce({\n        ok: true,\n        json: async () => createdUser,\n      });\n\n      const user = await service.createUser(newUser);\n\n      expect(user).toEqual(createdUser);\n      expect(fetch).toHaveBeenCalledWith(\n        'https:\u002F\u002Fapi.example.com\u002Fusers',\n        expect.objectContaining({\n          method: 'POST',\n          body: JSON.stringify(newUser),\n        })\n      );\n    });\n  });\n});\n```\n\n## Mocking Patterns\n\n### Pattern 1: Mocking Modules\n\n```typescript\n\u002F\u002F services\u002Femail.service.ts\nimport nodemailer from 'nodemailer';\n\nexport class EmailService {\n  private transporter = nodemailer.createTransport({\n    host: process.env.SMTP_HOST,\n    port: 587,\n    auth: {\n      user: process.env.SMTP_USER,\n      pass: process.env.SMTP_PASS,\n    },\n  });\n\n  async sendEmail(to: string, subject: string, html: string) {\n    await this.transporter.sendMail({\n      from: process.env.EMAIL_FROM,\n      to,\n      subject,\n      html,\n    });\n  }\n}\n\n\u002F\u002F services\u002Femail.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EmailService } from '.\u002Femail.service';\n\nvi.mock('nodemailer', () => ({\n  default: {\n    createTransport: vi.fn(() => ({\n      sendMail: vi.fn().mockResolvedValue({ messageId: '123' }),\n    })),\n  },\n}));\n\ndescribe('EmailService', () => {\n  let service: EmailService;\n\n  beforeEach(() => {\n    service = new EmailService();\n  });\n\n  it('should send email successfully', async () => {\n    await service.sendEmail(\n      'test@example.com',\n      'Test Subject',\n      '\u003Cp>Test Body\u003C\u002Fp>'\n    );\n\n    expect(service['transporter'].sendMail).toHaveBeenCalledWith(\n      expect.objectContaining({\n        to: 'test@example.com',\n        subject: 'Test Subject',\n      })\n    );\n  });\n});\n```\n\n### Pattern 2: Dependency Injection for Testing\n\n```typescript\n\u002F\u002F services\u002Fuser.service.ts\nexport interface IUserRepository {\n  findById(id: string): Promise\u003CUser | null>;\n  create(user: User): Promise\u003CUser>;\n}\n\nexport class UserService {\n  constructor(private userRepository: IUserRepository) {}\n\n  async getUser(id: string): Promise\u003CUser> {\n    const user = await this.userRepository.findById(id);\n    if (!user) {\n      throw new Error('User not found');\n    }\n    return user;\n  }\n\n  async createUser(userData: CreateUserDTO): Promise\u003CUser> {\n    \u002F\u002F Business logic here\n    const user = { id: generateId(), ...userData };\n    return this.userRepository.create(user);\n  }\n}\n\n\u002F\u002F services\u002Fuser.service.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { UserService, IUserRepository } from '.\u002Fuser.service';\n\ndescribe('UserService', () => {\n  let service: UserService;\n  let mockRepository: IUserRepository;\n\n  beforeEach(() => {\n    mockRepository = {\n      findById: vi.fn(),\n      create: vi.fn(),\n    };\n    service = new UserService(mockRepository);\n  });\n\n  describe('getUser', () => {\n    it('should return user if found', async () => {\n      const mockUser = { id: '1', name: 'John', email: 'john@example.com' };\n      vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);\n\n      const user = await service.getUser('1');\n\n      expect(user).toEqual(mockUser);\n      expect(mockRepository.findById).toHaveBeenCalledWith('1');\n    });\n\n    it('should throw error if user not found', async () => {\n      vi.mocked(mockRepository.findById).mockResolvedValue(null);\n\n      await expect(service.getUser('999')).rejects.toThrow('User not found');\n    });\n  });\n\n  describe('createUser', () => {\n    it('should create user successfully', async () => {\n      const userData = { name: 'John', email: 'john@example.com' };\n      const createdUser = { id: '1', ...userData };\n\n      vi.mocked(mockRepository.create).mockResolvedValue(createdUser);\n\n      const user = await service.createUser(userData);\n\n      expect(user).toEqual(createdUser);\n      expect(mockRepository.create).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n### Pattern 3: Spying on Functions\n\n```typescript\n\u002F\u002F utils\u002Flogger.ts\nexport const logger = {\n  info: (message: string) => console.log(`INFO: ${message}`),\n  error: (message: string) => console.error(`ERROR: ${message}`),\n};\n\n\u002F\u002F services\u002Forder.service.ts\nimport { logger } from '..\u002Futils\u002Flogger';\n\nexport class OrderService {\n  async processOrder(orderId: string): Promise\u003Cvoid> {\n    logger.info(`Processing order ${orderId}`);\n    \u002F\u002F Process order logic\n    logger.info(`Order ${orderId} processed successfully`);\n  }\n}\n\n\u002F\u002F services\u002Forder.service.test.ts\nimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { OrderService } from '.\u002Forder.service';\nimport { logger } from '..\u002Futils\u002Flogger';\n\ndescribe('OrderService', () => {\n  let service: OrderService;\n  let loggerSpy: any;\n\n  beforeEach(() => {\n    service = new OrderService();\n    loggerSpy = vi.spyOn(logger, 'info');\n  });\n\n  afterEach(() => {\n    loggerSpy.mockRestore();\n  });\n\n  it('should log order processing', async () => {\n    await service.processOrder('123');\n\n    expect(loggerSpy).toHaveBeenCalledWith('Processing order 123');\n    expect(loggerSpy).toHaveBeenCalledWith('Order 123 processed successfully');\n    expect(loggerSpy).toHaveBeenCalledTimes(2);\n  });\n});\n```\n\n## Integration Testing\n\n### Pattern 1: API Integration Tests\n\n```typescript\n\u002F\u002F tests\u002Fintegration\u002Fuser.api.test.ts\nimport request from 'supertest';\nimport { app } from '..\u002F..\u002Fsrc\u002Fapp';\nimport { pool } from '..\u002F..\u002Fsrc\u002Fconfig\u002Fdatabase';\n\ndescribe('User API Integration Tests', () => {\n  beforeAll(async () => {\n    \u002F\u002F Setup test database\n    await pool.query('CREATE TABLE IF NOT EXISTS users (...)');\n  });\n\n  afterAll(async () => {\n    \u002F\u002F Cleanup\n    await pool.query('DROP TABLE IF EXISTS users');\n    await pool.end();\n  });\n\n  beforeEach(async () => {\n    \u002F\u002F Clear data before each test\n    await pool.query('TRUNCATE TABLE users CASCADE');\n  });\n\n  describe('POST \u002Fapi\u002Fusers', () => {\n    it('should create a new user', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'john@example.com',\n        password: 'password123',\n      };\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(201);\n\n      expect(response.body).toMatchObject({\n        name: userData.name,\n        email: userData.email,\n      });\n      expect(response.body).toHaveProperty('id');\n      expect(response.body).not.toHaveProperty('password');\n    });\n\n    it('should return 400 if email is invalid', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'invalid-email',\n        password: 'password123',\n      };\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(400);\n\n      expect(response.body).toHaveProperty('error');\n    });\n\n    it('should return 409 if email already exists', async () => {\n      const userData = {\n        name: 'John Doe',\n        email: 'john@example.com',\n        password: 'password123',\n      };\n\n      await request(app).post('\u002Fapi\u002Fusers').send(userData);\n\n      const response = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send(userData)\n        .expect(409);\n\n      expect(response.body.error).toContain('already exists');\n    });\n  });\n\n  describe('GET \u002Fapi\u002Fusers\u002F:id', () => {\n    it('should get user by id', async () => {\n      const createResponse = await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send({\n          name: 'John Doe',\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const userId = createResponse.body.id;\n\n      const response = await request(app)\n        .get(`\u002Fapi\u002Fusers\u002F${userId}`)\n        .expect(200);\n\n      expect(response.body).toMatchObject({\n        id: userId,\n        name: 'John Doe',\n        email: 'john@example.com',\n      });\n    });\n\n    it('should return 404 if user not found', async () => {\n      await request(app)\n        .get('\u002Fapi\u002Fusers\u002F999')\n        .expect(404);\n    });\n  });\n\n  describe('Authentication', () => {\n    it('should require authentication for protected routes', async () => {\n      await request(app)\n        .get('\u002Fapi\u002Fusers\u002Fme')\n        .expect(401);\n    });\n\n    it('should allow access with valid token', async () => {\n      \u002F\u002F Create user and login\n      await request(app)\n        .post('\u002Fapi\u002Fusers')\n        .send({\n          name: 'John Doe',\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const loginResponse = await request(app)\n        .post('\u002Fapi\u002Fauth\u002Flogin')\n        .send({\n          email: 'john@example.com',\n          password: 'password123',\n        });\n\n      const token = loginResponse.body.token;\n\n      const response = await request(app)\n        .get('\u002Fapi\u002Fusers\u002Fme')\n        .set('Authorization', `Bearer ${token}`)\n        .expect(200);\n\n      expect(response.body.email).toBe('john@example.com');\n    });\n  });\n});\n```\n\n### Pattern 2: Database Integration Tests\n\n```typescript\n\u002F\u002F tests\u002Fintegration\u002Fuser.repository.test.ts\nimport { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';\nimport { Pool } from 'pg';\nimport { UserRepository } from '..\u002F..\u002Fsrc\u002Frepositories\u002Fuser.repository';\n\ndescribe('UserRepository Integration Tests', () => {\n  let pool: Pool;\n  let repository: UserRepository;\n\n  beforeAll(async () => {\n    pool = new Pool({\n      host: 'localhost',\n      port: 5432,\n      database: 'test_db',\n      user: 'test_user',\n      password: 'test_password',\n    });\n\n    repository = new UserRepository(pool);\n\n    \u002F\u002F Create tables\n    await pool.query(`\n      CREATE TABLE IF NOT EXISTS users (\n        id SERIAL PRIMARY KEY,\n        name VARCHAR(255) NOT NULL,\n        email VARCHAR(255) UNIQUE NOT NULL,\n        password VARCHAR(255) NOT NULL,\n        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n      )\n    `);\n  });\n\n  afterAll(async () => {\n    await pool.query('DROP TABLE IF EXISTS users');\n    await pool.end();\n  });\n\n  beforeEach(async () => {\n    await pool.query('TRUNCATE TABLE users CASCADE');\n  });\n\n  it('should create a user', async () => {\n    const user = await repository.create({\n      name: 'John Doe',\n      email: 'john@example.com',\n      password: 'hashed_password',\n    });\n\n    expect(user).toHaveProperty('id');\n    expect(user.name).toBe('John Doe');\n    expect(user.email).toBe('john@example.com');\n  });\n\n  it('should find user by email', async () => {\n    await repository.create({\n      name: 'John Doe',\n      email: 'john@example.com',\n      password: 'hashed_password',\n    });\n\n    const user = await repository.findByEmail('john@example.com');\n\n    expect(user).toBeTruthy();\n    expect(user?.name).toBe('John Doe');\n  });\n\n  it('should return null if user not found', async () => {\n    const user = await repository.findByEmail('nonexistent@example.com');\n    expect(user).toBeNull();\n  });\n});\n```\n\n## Frontend Testing with Testing Library\n\n### Pattern 1: React Component Testing\n\n```typescript\n\u002F\u002F components\u002FUserForm.tsx\nimport { useState } from 'react';\n\ninterface Props {\n  onSubmit: (user: { name: string; email: string }) => void;\n}\n\nexport function UserForm({ onSubmit }: Props) {\n  const [name, setName] = useState('');\n  const [email, setEmail] = useState('');\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    onSubmit({ name, email });\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput\n        type=\"text\"\n        placeholder=\"Name\"\n        value={name}\n        onChange={(e) => setName(e.target.value)}\n        data-testid=\"name-input\"\n      \u002F>\n      \u003Cinput\n        type=\"email\"\n        placeholder=\"Email\"\n        value={email}\n        onChange={(e) => setEmail(e.target.value)}\n        data-testid=\"email-input\"\n      \u002F>\n      \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n\n\u002F\u002F components\u002FUserForm.test.tsx\nimport { render, screen, fireEvent } from '@testing-library\u002Freact';\nimport { describe, it, expect, vi } from 'vitest';\nimport { UserForm } from '.\u002FUserForm';\n\ndescribe('UserForm', () => {\n  it('should render form inputs', () => {\n    render(\u003CUserForm onSubmit={vi.fn()} \u002F>);\n\n    expect(screen.getByPlaceholderText('Name')).toBeInTheDocument();\n    expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();\n    expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();\n  });\n\n  it('should update input values', () => {\n    render(\u003CUserForm onSubmit={vi.fn()} \u002F>);\n\n    const nameInput = screen.getByTestId('name-input') as HTMLInputElement;\n    const emailInput = screen.getByTestId('email-input') as HTMLInputElement;\n\n    fireEvent.change(nameInput, { target: { value: 'John Doe' } });\n    fireEvent.change(emailInput, { target: { value: 'john@example.com' } });\n\n    expect(nameInput.value).toBe('John Doe');\n    expect(emailInput.value).toBe('john@example.com');\n  });\n\n  it('should call onSubmit with form data', () => {\n    const onSubmit = vi.fn();\n    render(\u003CUserForm onSubmit={onSubmit} \u002F>);\n\n    fireEvent.change(screen.getByTestId('name-input'), {\n      target: { value: 'John Doe' },\n    });\n    fireEvent.change(screen.getByTestId('email-input'), {\n      target: { value: 'john@example.com' },\n    });\n    fireEvent.click(screen.getByRole('button', { name: 'Submit' }));\n\n    expect(onSubmit).toHaveBeenCalledWith({\n      name: 'John Doe',\n      email: 'john@example.com',\n    });\n  });\n});\n```\n\n### Pattern 2: Testing Hooks\n\n```typescript\n\u002F\u002F hooks\u002FuseCounter.ts\nimport { useState, useCallback } from 'react';\n\nexport function useCounter(initialValue = 0) {\n  const [count, setCount] = useState(initialValue);\n\n  const increment = useCallback(() => setCount((c) => c + 1), []);\n  const decrement = useCallback(() => setCount((c) => c - 1), []);\n  const reset = useCallback(() => setCount(initialValue), [initialValue]);\n\n  return { count, increment, decrement, reset };\n}\n\n\u002F\u002F hooks\u002FuseCounter.test.ts\nimport { renderHook, act } from '@testing-library\u002Freact';\nimport { describe, it, expect } from 'vitest';\nimport { useCounter } from '.\u002FuseCounter';\n\ndescribe('useCounter', () => {\n  it('should initialize with default value', () => {\n    const { result } = renderHook(() => useCounter());\n    expect(result.current.count).toBe(0);\n  });\n\n  it('should initialize with custom value', () => {\n    const { result } = renderHook(() => useCounter(10));\n    expect(result.current.count).toBe(10);\n  });\n\n  it('should increment count', () => {\n    const { result } = renderHook(() => useCounter());\n\n    act(() => {\n      result.current.increment();\n    });\n\n    expect(result.current.count).toBe(1);\n  });\n\n  it('should decrement count', () => {\n    const { result } = renderHook(() => useCounter(5));\n\n    act(() => {\n      result.current.decrement();\n    });\n\n    expect(result.current.count).toBe(4);\n  });\n\n  it('should reset to initial value', () => {\n    const { result } = renderHook(() => useCounter(10));\n\n    act(() => {\n      result.current.increment();\n      result.current.increment();\n    });\n\n    expect(result.current.count).toBe(12);\n\n    act(() => {\n      result.current.reset();\n    });\n\n    expect(result.current.count).toBe(10);\n  });\n});\n```\n\n## Test Fixtures and Factories\n\n```typescript\n\u002F\u002F tests\u002Ffixtures\u002Fuser.fixture.ts\nimport { faker } from '@faker-js\u002Ffaker';\n\nexport function createUserFixture(overrides?: Partial\u003CUser>): User {\n  return {\n    id: faker.string.uuid(),\n    name: faker.person.fullName(),\n    email: faker.internet.email(),\n    createdAt: faker.date.past(),\n    ...overrides,\n  };\n}\n\nexport function createUsersFixture(count: number): User[] {\n  return Array.from({ length: count }, () => createUserFixture());\n}\n\n\u002F\u002F Usage in tests\nimport { createUserFixture, createUsersFixture } from '..\u002Ffixtures\u002Fuser.fixture';\n\ndescribe('UserService', () => {\n  it('should process user', () => {\n    const user = createUserFixture({ name: 'John Doe' });\n    \u002F\u002F Use user in test\n  });\n\n  it('should handle multiple users', () => {\n    const users = createUsersFixture(10);\n    \u002F\u002F Use users in test\n  });\n});\n```\n\n## Snapshot Testing\n\n```typescript\n\u002F\u002F components\u002FUserCard.test.tsx\nimport { render } from '@testing-library\u002Freact';\nimport { describe, it, expect } from 'vitest';\nimport { UserCard } from '.\u002FUserCard';\n\ndescribe('UserCard', () => {\n  it('should match snapshot', () => {\n    const user = {\n      id: '1',\n      name: 'John Doe',\n      email: 'john@example.com',\n      avatar: 'https:\u002F\u002Fexample.com\u002Favatar.jpg',\n    };\n\n    const { container } = render(\u003CUserCard user={user} \u002F>);\n\n    expect(container.firstChild).toMatchSnapshot();\n  });\n\n  it('should match snapshot with loading state', () => {\n    const { container } = render(\u003CUserCard loading \u002F>);\n    expect(container.firstChild).toMatchSnapshot();\n  });\n});\n```\n\n## Coverage Reports\n\n```typescript\n\u002F\u002F package.json\n{\n  \"scripts\": {\n    \"test\": \"vitest\",\n    \"test:coverage\": \"vitest --coverage\",\n    \"test:ui\": \"vitest --ui\"\n  }\n}\n```\n\n## Best Practices\n\n1. **Follow AAA Pattern**: Arrange, Act, Assert\n2. **One assertion per test**: Or logically related assertions\n3. **Descriptive test names**: Should describe what is being tested\n4. **Use beforeEach\u002FafterEach**: For setup and teardown\n5. **Mock external dependencies**: Keep tests isolated\n6. **Test edge cases**: Not just happy paths\n7. **Avoid implementation details**: Test behavior, not implementation\n8. **Use test factories**: For consistent test data\n9. **Keep tests fast**: Mock slow operations\n10. **Write tests first (TDD)**: When possible\n11. **Maintain test coverage**: Aim for 80%+ coverage\n12. **Use TypeScript**: For type-safe tests\n13. **Test error handling**: Not just success cases\n14. **Use data-testid sparingly**: Prefer semantic queries\n15. **Clean up after tests**: Prevent test pollution\n\n## Common Patterns\n\n### Test Organization\n\n```typescript\ndescribe('UserService', () => {\n  describe('createUser', () => {\n    it('should create user successfully', () => {});\n    it('should throw error if email exists', () => {});\n    it('should hash password', () => {});\n  });\n\n  describe('updateUser', () => {\n    it('should update user', () => {});\n    it('should throw error if not found', () => {});\n  });\n});\n```\n\n### Testing Promises\n\n```typescript\n\u002F\u002F Using async\u002Fawait\nit('should fetch user', async () => {\n  const user = await service.fetchUser('1');\n  expect(user).toBeDefined();\n});\n\n\u002F\u002F Testing rejections\nit('should throw error', async () => {\n  await expect(service.fetchUser('invalid')).rejects.toThrow('Not found');\n});\n```\n\n### Testing Timers\n\n```typescript\nimport { vi } from 'vitest';\n\nit('should call function after delay', () => {\n  vi.useFakeTimers();\n\n  const callback = vi.fn();\n  setTimeout(callback, 1000);\n\n  expect(callback).not.toHaveBeenCalled();\n\n  vi.advanceTimersByTime(1000);\n\n  expect(callback).toHaveBeenCalled();\n\n  vi.useRealTimers();\n});\n```\n\n## Resources\n\n- **Jest Documentation**: https:\u002F\u002Fjestjs.io\u002F\n- **Vitest Documentation**: https:\u002F\u002Fvitest.dev\u002F\n- **Testing Library**: https:\u002F\u002Ftesting-library.com\u002F\n- **Kent C. Dodds Testing Blog**: https:\u002F\u002Fkentcdodds.com\u002Fblog\u002F\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fjavascript-testing-patterns",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill javascript-testing-patterns",{"tool":67,"command":68},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill javascript-testing-patterns",{"tool":70,"command":71},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill javascript-testing-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fjavascript-testing-patterns\u002FSKILL.md",true,[75],{"path":76,"resourceType":77,"resourceTypeDesc":78,"sizeBytes":79,"sourceHtmlUrl":80},"skill-report.json","REFERENCE","参考文档",19901,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fjavascript-testing-patterns\u002Fskill-report.json",[82,94,104,113,119,126],{"skillNo":83,"name":84,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":90,"publishedAt":47},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[87],{"categoryNo":88,"categoryName":89,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[91,92,93],"model","model hierarchy","model hierarchy skill",{"skillNo":95,"name":96,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":100,"publishedAt":47},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[99],{"categoryNo":88,"categoryName":89,"taxonomyType":21,"taxonomyTypeDesc":22},[101,102,103],"MCP","zoom","zoom mcp",{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":47},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[110,111,112,102],"sdk","zoom apps sdk","apps",{"skillNo":114,"name":115,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":47},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[115],{"skillNo":120,"name":121,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":125,"publishedAt":47},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[124],{"categoryNo":88,"categoryName":89,"taxonomyType":21,"taxonomyTypeDesc":22},[121],{"skillNo":127,"name":128,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":47},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[128]]