atelier-typescript-testing
1
总安装量
1
周安装量
#53775
全站排名
安装命令
npx skills add https://github.com/martinffx/claude-code-atelier --skill atelier-typescript-testing
Agent 安装分布
opencode
1
Skill 文档
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/afterEachto ensure test isolation - Async/await over callbacks – Prefer
async/awaitfor 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