atelier-typescript-testing

TypeScript Testing Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "atelier-typescript-testing" with this command: npx skills add martinffx/claude-code-atelier/martinffx-claude-code-atelier-atelier-typescript-testing

TypeScript Testing Patterns

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: 'alice@example.com', })

// 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: 'alice@example.com', subject: 'Test' })

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

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

See 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: 'alice@example.com', }) }),

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 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 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

  • Test behavior, not implementation - Focus on what the code does, not how it does it

  • One assertion per test when possible - Makes failures clearer and tests more focused

  • Use typed mocks - vi.mocked<T>() provides type safety for mock setup and assertions

  • Mock at boundaries - Mock external dependencies (APIs, databases), not internal functions

  • Use MSW for API tests - Mock at the network level for realistic integration tests

  • Snapshots for complex output - Use snapshots for large objects, explicit assertions for critical values

  • Property matchers for dynamic values - Handle dates, IDs, timestamps with expect.any()

  • Clear test names - Describe the scenario and expected outcome: "creates user with valid data"

  • Setup/teardown for state - Use beforeEach /afterEach to ensure test isolation

  • Async/await over callbacks - Prefer async /await for cleaner async test code

  • Fake timers for time-based code - Use vi.useFakeTimers() to control time in tests

  • 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 (use MSW or in-memory DB)

  • 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('alice@example.com') })

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python:architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

atelier-typescript-drizzle-orm

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

python:build-tools

No summary provided by upstream source.

Repository SourceNeeds Review