[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70NTF48RYWBX7WQNGDPVCH7":3,"skill-same:01M70NTF48RYWBX7WQNGDPVCH7":76},{"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":59,"ownerDescription":60,"updatedAt":61,"install":62,"resources":75},"01M70NTF48RYWBX7WQNGDPVCH7","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.","shengdabai\u002FTony-Claude-Code-Skills","shengdabai","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F116417419?v=4","01M70F6D2WY5PQV5A92BKYJQEW","Tony Sheng","01M70F6D3GAAAYZ5H4CC1G8VXM",3,"MIT","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fwshobson-agents\u002Fplugins\u002Fjavascript-typescript\u002Fskills\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","patterns","javascript testing patterns","javascript","testing patterns","integration tests","infrastructure","library","typescript","strategies","jest","when","workflows","testing library","writing","implementing","up","end","unit","test-driven","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","7850740bd837adb32f0a109ab2af82482196c6f2","skills\u002Fwshobson-agents\u002Fplugins\u002Fjavascript-typescript\u002Fskills\u002Fjavascript-testing-patterns",1,"https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills","https:\u002F\u002Fzturnsgo.com","https:\u002F\u002Fgithub.com\u002Fshengdabai","https:\u002F\u002Fzturnsgo.com\u002F","AI × education creator — producer, not consumer. Taught 6,000+ learners; shipping learning tools & daily essays with Claude Code, practicing @xiaolai's way 🥋","2026-11-01 21:28:23",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":74},[64,67,70],{"tool":65,"command":66},"npx","npx skills add shengdabai\u002FTony-Claude-Code-Skills --skill javascript-testing-patterns",{"tool":68,"command":69},"bunx","bunx skills add shengdabai\u002FTony-Claude-Code-Skills --skill javascript-testing-patterns",{"tool":71,"command":72},"pnpm","pnpm dlx skills add shengdabai\u002FTony-Claude-Code-Skills --skill javascript-testing-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fwshobson-agents\u002Fplugins\u002Fjavascript-typescript\u002Fskills\u002Fjavascript-testing-patterns\u002FSKILL.md",true,[],[77,93,127,153,175,195],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":86,"publishedAt":47},"01M70P17DAD4C1C47NPH6A96EM","xiaolai-methodology","以李笑来(Li Xiaolai)的思考方式、方法论与表达风格,为用户做全方位 1-on-1 教练指导,覆盖创业、赚钱、投资、学习、自学、读书写作、用 AI、以及人生心态与自我管理。当用户提到 \"李笑来\"、\"笑来\"、\"笑来老师\"、\"扮演李笑来\"、\"找笑来聊聊\"、\"笑来怎么看\"、\"xiaolai\"、\"把时间当作朋友\"、\"通往财富自由之路\"、\"定投改变命运\"、\"韭菜的自我修养\"、\"自学是门手艺\"、\"让时间陪你慢慢变富\"、\"七年就是一辈子\"、\"元认知\"、\"活在未来\"、\"复利\u002F长期主义\"、\"践行\"、\"痴迷改进\"、\"个人商业模式\u002F出售时间\"、\"睡后收入\"、\"定投\"、\"财富自由\"、\"操作系统升级\"、\"最少必要知识\"、\"北极星提示词\"、\"用 AI 该怎么学\u002F思考\" 等任一触发词,或当用户带着关于创业\u002F赚钱\u002F投资\u002F学习\u002F职业\u002F重大人生决策的困惑、希望被人用笑来式的「逻辑 + 大白话 + 类比 + 重新定义 + 践行导向」来点醒和指导时,自动进入李笑来教练模式。","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fxiaolai-methodology\u002FSKILL.md",[83],{"categoryNo":84,"categoryName":85,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",[87,88,89,90,91,41,92],"methodology","xiaolai","xiaolai methodology","朋友","投资","收入",{"skillNo":94,"name":95,"nameZh":6,"description":96,"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":107,"publishedAt":47},"01M70P13NB9YP7ACJR1S861CDS","zread","Produce and consume a wiki-style knowledge base for a code repository via the `zread` CLI and its on-disk output under `.\u002F.zread\u002Fwiki\u002F`. Use this skill whenever the user wants to understand, onboard onto, explore, summarize, map, or get an overview of an unfamiliar codebase; asks for architecture docs, a project wiki, a repo walkthrough, module\u002Fpackage explanations, or \"what does this repo do\"; wants to generate, regenerate, resume, browse, or serve code documentation locally; or mentions zread \u002F zread.ai directly. Also use it proactively before diving into a large unknown repo — if `.\u002F.zread\u002Fwiki\u002Fcurrent` exists, read the generated pages instead of crawling source file-by-file; if it doesn't, consider offering to run `zread generate`. The trigger is the intent (understand a codebase through generated docs), not the literal word \"zread\".","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fzread\u002FSKILL.md",[99,102],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[95,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126],"knowledge","wiki","explore","walkthrough","project","get","knowledge base","run","generate","intent","cli","map","documentation","project wiki","disk","architecture","code","unknown","generated",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":137,"publishedAt":47},"01M70P10S4SWMZQ8BJAFBFDRW3","youtube","Launch the YouTube Creator Agent to auto-generate videos.\nUse when: user types \u002Fyoutube, wants to create YouTube videos,\ngenerate video content, or upload to YouTube.\nKeywords: youtube, video, 视频, 创作视频, 发布视频","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fyoutube\u002FSKILL.md",[133,134],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":135,"categoryName":136,"taxonomyType":105,"taxonomyTypeDesc":106},"01JBSEEDCAT000000000000036","内容创作者",[129,138,139,140,141,142,143,144,116,145,146,147,38,148,149,150,151,152],"user","content","视频","creator","generate video","videos","launch","video content","create","upload","use","auto","types","Video","创作",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":160,"publishedAt":47},"01M70P0YE95D8BACZCGV8BK90K","youtube-script-sync","Sync, update, or optimize Tony's GetNote knowledge base `Youtube视频逐字稿` by turning each parent recording or first-person idea note into an idempotent child note containing a directly recordable YouTube long-video or Shorts script. Use when the user says “更新\u002F同步\u002F优化 Youtube视频逐字稿知识库”, or says “更新\u002F同步\u002F优化该知识库” while this knowledge base is the active context.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fyoutube-script-sync\u002FSKILL.md",[159],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[161,162,129,163,164,151,165,166,167,168,169,170,171,172,114,173,38,148,108,174],"sync","script","youtube script sync","recording","shorts","first","base","person","update","active","child","optimize","idempotent","long",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":185,"publishedAt":47},"01M70P0WMV5XT0EEC0N445PW07","writing-skills","How to write SKILL.md files that trigger reliably and teach effectively. Use when creating, improving, or reviewing Claude Code skills.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fxiaolai\u002Fnlpm\u002Fskills\u002Fnlpm\u002Fwriting-skills\u002FSKILL.md",[181,182,183,184],{"categoryNo":84,"categoryName":85,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},{"categoryNo":135,"categoryName":136,"taxonomyType":105,"taxonomyTypeDesc":106},[186,41,187,188,189,190,124,38,148,191,192,193,194],"writing skills","how","teach","write skill","write","claude code skills","files","how to","trigger",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":199,"categories":200,"keywords":203,"publishedAt":47},"01M70P0SJFR4YVJQBGMHKGSKXT","writing-rules","How to write .claude\u002Frules\u002F files that Claude actually follows. Use when creating, improving, or reviewing project rules.","https:\u002F\u002Fgithub.com\u002Fshengdabai\u002FTony-Claude-Code-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fxiaolai\u002Fnlpm\u002Fskills\u002Fnlpm\u002Fwriting-rules\u002FSKILL.md",[201,202],{"categoryNo":84,"categoryName":85,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":135,"categoryName":136,"taxonomyType":105,"taxonomyTypeDesc":106},[204,41,205,112,206,192,38,187,190,148,193],"Rules","writing rules","project rules"]