martinffx/typescript-skills · Archived

typescript-testing

TypeScript testing patterns with Vitest and MSW. Use when writing unit tests, mocking APIs, creating typed mocks for dependency injection, or using snapshot testing.

First seen Aug 2, 2026

Installation

$ npx skills add martinffx/typescript-skills --skill typescript-testing

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from martinffx/typescript-skills.

npx skills add martinffx/typescript-skills

Browse all from martinffx/typescript-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,614 B
  • docs SUMMARY.md 191 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 6 installs

SKILL.md

TypeScript Testing Patterns

Inspect the owning package and existing implementation first. Reuse established project types, helpers, errors, lifecycle behavior, and test utilities. The patterns below are options, not an implementation checklist. Introduce one only when the current task requires it.

Project-specific rules

  • Test changed behavior and one contract per active boundary.
  • Do not repeat the same scenarios through routes, services, repositories, and

full-stack suites.

  • Reuse existing database test Layers and fixtures.
  • Create a reusable harness only after a second current consumer demonstrates the

same setup.

  • Before testing a wrapper extensively, question whether the wrapper should exist.

Comprehensive testing patterns using Vitest for unit testing, MSW for API mocking, and snapshot testing for complex object validation.

Quick Start

Installation

# Core testing dependencies
bun add -d vitest @vitest/ui

# MSW for API mocking
bun add -d msw

# Optional: coverage reporting
bun add -d @vitest/coverage-v8

Basic Test Structure

import { describe, it, expect, vi, beforeEach } from 'vitest'

describe('UserService', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('creates user with valid data', async () => {
    const result = await userService.create({ name: 'Alice' })
    expect(result).toMatchObject({ name: 'Alice' })
  })
})

Typed Mock Objects

Create type-safe mocks for dependency injection using vi.mocked():

import { vi } from 'vitest'
import type { UserRepository } from './user-repository'

// Mock the entire module
vi.mock('./user-repository')

// Get typed mock instance
const mockUserRepo = vi.mocked<UserRepository>({
  findById: vi.fn(),
  save: vi.fn(),
  delete: vi.fn(),
})

// Type-safe mock return values
mockUserRepo.findById.mockResolvedValue({
  id: '123',
  name: 'Alice',
  email: '[email protected]',
})

// Assertions with full type safety
expect(mockUserRepo.findById).toHaveBeenCalledWith('123')

Mock Return Values

// Single return value
mockRepo.findById.mockResolvedValue(user)

// Multiple calls, different returns
mockRepo.findById
  .mockResolvedValueOnce(null)
  .mockResolvedValueOnce(user)

// Conditional logic
mockRepo.findById.mockImplementation(async (id) => {
  if (id === '123') return user
  return null
})

// Throw errors
mockRepo.save.mockRejectedValue(new Error('Database error'))

Spy on Real Implementations

import { vi } from 'vitest'
import { emailService } from './email-service'

// Spy on method without replacing it
vi.spyOn(emailService, 'send')

// Call real implementation
await emailService.send({ to: '[email protected]', subject: 'Test' })

// Assert it was called
expect(emailService.send).toHaveBeenCalledOnce()

// Temporarily override return value
emailService.send.mockResolvedValueOnce({ messageId: 'test-123' })

See [references/mocking.md](./references/mocking.md) for comprehensive mocking patterns including module mocks, class mocks, stateful handlers, and dependency injection.

API Mocking with MSW

Mock HTTP requests at the network level for integration tests:

import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { afterAll, afterEach, beforeAll } from 'vitest'

// Define handlers
const handlers = [
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Alice',
      email: '[email protected]',
    })
  }),

  http.post('/api/users', async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json({ id: '123', ...body }, { status: 201 })
  }),
]

// Setup server
const server = setupServer(...handlers)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

See [references/msw.md](./references/msw.md) for advanced MSW patterns including error simulation, delays, and per-test overrides.

Snapshot Testing

Validate complex objects and output without manual assertions:

import { it, expect } from 'vitest'

it('generates correct user profile', () => {
  const profile = generateUserProfile(user)

  // Snapshot entire object
  expect(profile).toMatchSnapshot()
})

// Handle dynamic values (dates, IDs)
it('creates order with timestamp', () => {
  const order = createOrder(items)

  expect(order).toMatchSnapshot({
    id: expect.any(String),
    createdAt: expect.any(Date),
  })
})

// Inline snapshots for small objects
it('formats error message', () => {
  const error = formatError(new Error('Failed'))
  expect(error).toMatchInlineSnapshot(`
    {
      "message": "Failed",
      "code": "UNKNOWN_ERROR",
    }
  `)
})

See [references/snapshot-testing.md](./references/snapshot-testing.md) for snapshot maintenance, custom serializers, and best practices.

Testing Async Code

// Promises
it('loads user data', async () => {
  const user = await userService.findById('123')
  expect(user).toBeDefined()
})

// Callbacks
it('calls callback on completion', () => {
  return new Promise<void>((resolve) => {
    processData(data, (result) => {
      expect(result).toBe(expected)
      resolve()
    })
  })
})

// Timers
it('retries after delay', async () => {
  vi.useFakeTimers()

  const promise = retryOperation()
  vi.advanceTimersByTime(1000)

  const result = await promise
  expect(result).toBe('success')

  vi.useRealTimers()
})

Test Organization

// Group related tests
describe('UserService', () => {
  describe('create', () => {
    it('succeeds with valid data', () => {})
    it('throws on duplicate email', () => {})
  })

  describe('update', () => {
    it('updates existing user', () => {})
    it('throws on not found', () => {})
  })
})

// Shared setup
describe('authenticated requests', () => {
  beforeEach(() => {
    mockAuth.isAuthenticated.mockReturnValue(true)
  })

  it('allows user creation', () => {})
  it('allows user deletion', () => {})
})

Guidelines

  1. Test behavior, not implementation - Focus on what the code does, not how it does it
  2. Use enough assertions to verify one behavior - Keep related evidence together
  3. Use typed mocks - vi.mocked<T>() provides type safety for mock setup and assertions
  4. Mock at boundaries - Mock external dependencies (APIs, databases), not internal functions
  5. Use MSW for API tests - Mock at the network level for realistic integration tests
  6. Snapshots for complex output - Use snapshots for large objects, explicit assertions for critical values
  7. Property matchers for dynamic values - Handle dates, IDs, timestamps with expect.any()
  8. Clear test names - Describe the scenario and expected outcome: "creates user with valid data"
  9. Setup/teardown for state - Use beforeEach/afterEach to ensure test isolation
  10. Async/await over callbacks - Prefer async/await for cleaner async test code
  11. Fake timers for time-based code - Use vi.useFakeTimers() to control time in tests
  12. Test error cases - Verify error handling with expect().rejects.toThrow()

Related Skills

  • build-tools - Vitest configuration, test scripts, coverage setup
  • api-design - Testing REST API contracts and error responses
  • fastify - Testing Fastify routes and plugins
  • drizzle-orm - Testing database queries with the project's existing database

test Layer or fixture

  • dynamodb-toolbox - Testing DynamoDB entities and queries

Common Patterns

Testing with Dependency Injection

class UserService {
  constructor(
    private repo: UserRepository,
    private email: EmailService,
  ) {}

  async create(data: CreateUserInput) {
    const user = await this.repo.save(data)
    await this.email.sendWelcome(user.email)
    return user
  }
}

// Test with mocks
it('sends welcome email on create', async () => {
  const mockRepo = vi.mocked<UserRepository>({
    save: vi.fn().mockResolvedValue(savedUser),
  })
  const mockEmail = vi.mocked<EmailService>({
    sendWelcome: vi.fn().mockResolvedValue(undefined),
  })

  const service = new UserService(mockRepo, mockEmail)
  await service.create(userData)

  expect(mockEmail.sendWelcome).toHaveBeenCalledWith('[email protected]')
})

Testing Error Boundaries

it('handles repository errors gracefully', async () => {
  mockRepo.save.mockRejectedValue(new Error('Database error'))

  await expect(
    service.create(userData)
  ).rejects.toThrow('Failed to create user')

  // Verify cleanup or rollback occurred
  expect(mockEmail.sendWelcome).not.toHaveBeenCalled()
})

Testing Race Conditions

it('handles concurrent requests correctly', async () => {
  const promises = [
    service.processOrder(order1),
    service.processOrder(order2),
    service.processOrder(order3),
  ]

  const results = await Promise.all(promises)

  expect(results).toHaveLength(3)
  expect(new Set(results.map(r => r.id)).size).toBe(3)
})

Debugging Tests

# Run single test file
vitest run path/to/test.spec.ts

# Run tests matching pattern
vitest run -t "creates user"

# Watch mode for TDD
vitest watch

# UI mode for debugging
vitest --ui

# Coverage report
vitest run --coverage