typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
Best use case
typescript-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
Teams using typescript-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/typescript-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How typescript-testing Compares
| Feature / Agent | typescript-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# TypeScript Testing
Core testing patterns for TypeScript and JavaScript projects using Vitest, Testing Library, MSW, and Playwright.
## When to Activate
- Setting up a test suite for a TypeScript/JavaScript project
- Writing unit, integration, or component tests
- Configuring Vitest, Jest, or Playwright
- Mocking APIs with MSW
- Achieving 80%+ code coverage
- Migrating from Jest to Vitest on an ESM or Vite-based project
- Debugging tests that share mock state across runs and fail non-deterministically
- Setting up React Testing Library with `userEvent` for realistic component interaction testing
## Framework Selection
| Use Case | Tool | Why |
|---|---|---|
| Unit + Integration | **Vitest** | Native ESM, fast, compatible with Vite projects |
| Unit + Integration (legacy) | **Jest** | Mature ecosystem, CJS/CommonJS projects |
| React component tests | **Testing Library** + Vitest | User-centric assertions |
| API mocking | **MSW** (Mock Service Worker) | Intercepts at network level, works in browser + Node |
| E2E | **Playwright** | See `e2e-testing` skill |
## Vitest Setup
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node', // or 'jsdom' for browser APIs
coverage: {
provider: 'v8',
thresholds: { lines: 80, branches: 80, functions: 80, statements: 80 },
exclude: ['node_modules', 'dist', '**/*.d.ts', '**/*.config.*'],
},
},
})
```
## Unit Test Patterns
```typescript
// src/lib/format-price.test.ts
import { describe, it, expect } from 'vitest'
import { formatPrice } from './format-price'
describe('formatPrice', () => {
it('formats USD correctly', () => {
expect(formatPrice(1000, 'USD')).toBe('$10.00')
})
it('handles zero', () => {
expect(formatPrice(0, 'USD')).toBe('$0.00')
})
it('throws on negative amount', () => {
expect(() => formatPrice(-1, 'USD')).toThrow('Amount must be non-negative')
})
})
```
## Mocking with vi.mock()
```typescript
// Mock a module
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { sendEmail } from './email-service'
vi.mock('./email-service', () => ({
sendEmail: vi.fn().mockResolvedValue({ messageId: 'test-123' }),
}))
describe('notifyUser', () => {
beforeEach(() => vi.clearAllMocks())
it('calls sendEmail with correct args', async () => {
await notifyUser('user@example.com', 'Welcome!')
expect(sendEmail).toHaveBeenCalledWith({
to: 'user@example.com',
subject: 'Welcome!',
})
})
})
```
## React Component Testing (Testing Library)
```typescript
// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Button } from './Button'
describe('Button', () => {
it('calls onClick when clicked', async () => {
const user = userEvent.setup()
const onClick = vi.fn()
render(<Button onClick={onClick}>Click me</Button>)
await user.click(screen.getByRole('button', { name: /click me/i }))
expect(onClick).toHaveBeenCalledOnce()
})
it('is disabled when loading', () => {
render(<Button loading>Submit</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})
```
**Testing Library Principles:**
- Query by role (`getByRole`) over test IDs
- Query by label text for form inputs (`getByLabelText`)
- Use `userEvent` over `fireEvent` for realistic interactions
- Never query by CSS class or internal implementation details
## API Mocking with MSW
```typescript
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({ id: params.id, name: 'Test User' })
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json()
return HttpResponse.json({ id: 'new-123', ...body }, { status: 201 })
}),
]
// src/mocks/server.ts (Node.js / Vitest)
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// vitest.setup.ts
import { beforeAll, afterAll, afterEach } from 'vitest'
import { server } from './src/mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
```
## Integration Tests (API Endpoints)
```typescript
// Using Supertest with Express/Fastify
import request from 'supertest'
import { app } from '../src/app'
describe('GET /api/users/:id', () => {
it('returns user when found', async () => {
const res = await request(app).get('/api/users/1').expect(200)
expect(res.body).toMatchObject({ id: '1', name: expect.any(String) })
})
it('returns 404 when not found', async () => {
await request(app).get('/api/users/999').expect(404)
})
})
```
## Coverage
```bash
# Run with coverage
vitest run --coverage
# Coverage thresholds in vitest.config.ts enforce 80%+ on CI
# Check specific file
vitest run --coverage --reporter=verbose src/lib/
```
## TDD Cycle
1. **RED**: Write failing test that describes the desired behaviour
2. **GREEN**: Write minimal implementation to make test pass — no more
3. **REFACTOR**: Clean up implementation without breaking tests
4. **VERIFY**: `vitest run --coverage` — confirm 80%+ coverage
## Anti-Patterns
### Testing Implementation Details Instead of Behaviour
**Wrong:**
```typescript
it('sets internal loading state', () => {
const service = new UserService()
service.fetchUser('1')
expect(service['_isLoading']).toBe(true) // private field access
})
```
**Correct:**
```typescript
it('shows loading indicator while fetching', async () => {
render(<UserProfile id="1" />)
expect(screen.getByRole('progressbar')).toBeInTheDocument()
await screen.findByText('John Doe')
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
})
```
**Why:** Tests tied to internal state break on refactors even when behaviour is unchanged; only test what the user or caller observes.
### Using `fireEvent` Instead of `userEvent` for Interactions
**Wrong:**
```typescript
fireEvent.click(screen.getByRole('button', { name: /submit/i }))
fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'a@b.com' } })
```
**Correct:**
```typescript
const user = userEvent.setup()
await user.type(screen.getByLabelText('Email'), 'a@b.com')
await user.click(screen.getByRole('button', { name: /submit/i }))
```
**Why:** `fireEvent` dispatches synthetic events that skip browser input processing (blur, focus, input validation), causing false positives that miss real-world bugs.
### Mocking Internal Module Functions Instead of Boundaries
**Wrong:**
```typescript
vi.mock('./user-service', () => ({ getUser: vi.fn().mockResolvedValue(mockUser) }))
vi.mock('./format-date', () => ({ formatDate: vi.fn().mockReturnValue('Jan 1') }))
```
**Correct:**
```typescript
// Mock at the network boundary with MSW
server.use(
http.get('/api/users/:id', () => HttpResponse.json(mockUser))
)
```
**Why:** Mocking internal helpers couples tests to the call graph; mocking at the network/DB boundary keeps tests robust to internal refactors.
### Skipping `beforeEach` Cleanup Leading to Shared State
**Wrong:**
```typescript
const mockFn = vi.fn()
vi.mock('./logger', () => ({ log: mockFn }))
it('logs on success', async () => { await doWork(); expect(mockFn).toHaveBeenCalledOnce() })
it('logs on error', async () => { await doWork(); expect(mockFn).toHaveBeenCalledOnce() }) // fails: called twice
```
**Correct:**
```typescript
beforeEach(() => vi.clearAllMocks())
it('logs on success', async () => { await doWork(); expect(mockFn).toHaveBeenCalledOnce() })
it('logs on error', async () => { await doWork(); expect(mockFn).toHaveBeenCalledOnce() })
```
**Why:** Without clearing mocks between tests, call counts accumulate across the suite, causing order-dependent failures.
### Asserting on Snapshot Blobs for Business Logic
**Wrong:**
```typescript
it('formats user response', () => {
expect(formatUser(raw)).toMatchSnapshot() // brittle mega-snapshot
})
```
**Correct:**
```typescript
it('formats user response', () => {
const result = formatUser(raw)
expect(result.name).toBe('John Doe')
expect(result.email).toBe('john@example.com')
expect(result).not.toHaveProperty('password')
})
```
**Why:** Snapshot tests on complex objects silently accept wrong output after regeneration; explicit assertions document exactly what matters.
## Common Pitfalls
- **Don't test implementation details**: Test observable behaviour, not internal state
- **Avoid `act()` warnings**: Use `userEvent` from `@testing-library/user-event`, not `@testing-library/react`
- **Isolate tests**: Each test must be independent — use `beforeEach`/`afterEach` to reset state
- **Mock at the boundary**: Mock network/DB/external services, not internal functionsRelated Skills
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
typescript-patterns
TypeScript patterns — type system best practices, strict mode, utility types, generics, discriminated unions, error handling with Result types, and module organization. Core patterns for production TypeScript.
typescript-patterns-advanced
Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.
typescript-monorepo-patterns
TypeScript monorepo patterns with Turborepo + pnpm workspaces. Covers package structure, shared configs, task pipeline caching, build orchestration, and publishing strategy.
swift-testing
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
swift-protocol-di-testing
Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
scala-testing
Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.
rust-testing
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
rust-testing-advanced
Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with
ruby-testing
RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.
r-testing
R testing patterns: testthat 3e with expect_* assertions, snapshot testing, mocking with mockery and httptest2, covr code coverage, lintr static analysis, property-based testing with hedgehog, testing Shiny apps with shinytest2. Use when writing or reviewing R tests.
python-testing
Python testing strategies using pytest, TDD methodology, fixtures, mocking, and parametrization. Core testing fundamentals.