@mockmaster/msw-adapter
Record & replay engine
npm install @mockmaster/msw-adapterOverview
The MSW adapter package provides the core record and replay functionality for MockMaster.
Key Features:
- Create scenarios and recordings
- Replay handlers for request matching
- Request/response pair management
- Type-safe recording creation
- Deterministic response generation
API Reference
createScenario(name, description)
Create an empty scenario.
import { createScenario } from '@mockmaster/msw-adapter'
const scenario = createScenario('user-api', 'User API endpoints')
console.log(scenario)
// {
// name: 'user-api',
// description: 'User API endpoints',
// recordings: [],
// createdAt: 1234567890,
// updatedAt: 1234567890
// }Parameters:
name(string) - Scenario namedescription(string) - Scenario description
Returns: Scenario object
createRecording(request, response)
Create a recording of a request/response pair.
import { createRecording } from '@mockmaster/msw-adapter'
const recording = createRecording(
{
method: 'GET',
url: 'https://api.example.com/users/123',
path: '/users/:id',
timestamp: Date.now()
},
{
status: 200,
body: { id: 123, name: 'John Doe' },
headers: { 'content-type': 'application/json' },
timestamp: Date.now()
}
)Parameters:
request(object) - Request detailsresponse(object) - Response details
Returns: Recording object
Request Properties:
method(string) - HTTP methodurl(string) - Full URLpath(string) - Path portion (can include parameters)timestamp(number) - Request timestampbody(any, optional) - Request bodyheaders(object, optional) - Request headers
Response Properties:
status(number) - HTTP status codebody(any) - Response bodytimestamp(number) - Response timestampheaders(object, optional) - Response headers
addRecordingToScenario(scenario, recording)
Add a recording to a scenario.
import { addRecordingToScenario } from '@mockmaster/msw-adapter'
let scenario = createScenario('api', 'API endpoints')
const recording = createRecording(
{ method: 'GET', url: 'https://api.example.com/users', path: '/users', timestamp: Date.now() },
{ status: 200, body: [], timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, recording)
console.log(scenario.recordings.length) // 1Parameters:
scenario(Scenario) - Scenario to add torecording(Recording) - Recording to add
Returns: New scenario with recording added
createReplayHandler(scenario)
Create a replay handler from a scenario.
import { createReplayHandler } from '@mockmaster/msw-adapter'
const handler = createReplayHandler(scenario)
// Replay requests
const response = handler({
method: 'GET',
path: '/users/123'
})
console.log(response.status) // 200
console.log(response.body) // { id: 123, name: 'John Doe' }Parameters:
scenario(Scenario) - Scenario to create handler from
Returns: Handler function
Handler Function:
- Takes request object:
{ method: string, path: string, body?: any } - Returns response object or
undefinedif no match
Types
Scenario
interface Scenario {
name: string
description: string
recordings: Recording[]
createdAt: number
updatedAt: number
}Recording
interface Recording {
request: {
method: string
url: string
path: string
timestamp: number
body?: any
headers?: Record<string, string>
}
response: {
status: number
body: any
timestamp: number
headers?: Record<string, string>
}
}ReplayHandler
type ReplayHandler = (request: {
method: string
path: string
body?: any
}) => {
status: number
body: any
headers?: Record<string, string>
} | undefinedUsage Examples
Basic Recording
import {
createScenario,
createRecording,
addRecordingToScenario,
createReplayHandler
} from '@mockmaster/msw-adapter'
// Create scenario
let scenario = createScenario('user-api', 'User API operations')
// Create recording
const getUserRecording = createRecording(
{
method: 'GET',
url: 'https://api.example.com/users/1',
path: '/users/:id',
timestamp: Date.now()
},
{
status: 200,
body: {
id: 1,
name: 'John Doe',
email: 'john@example.com'
},
headers: {
'content-type': 'application/json'
},
timestamp: Date.now()
}
)
// Add to scenario
scenario = addRecordingToScenario(scenario, getUserRecording)
// Create handler
const handler = createReplayHandler(scenario)
// Use in test
const response = handler({ method: 'GET', path: '/users/1' })
expect(response.status).toBe(200)
expect(response.body.name).toBe('John Doe')Multiple Recordings
let scenario = createScenario('rest-api', 'Complete REST API')
// GET /users
const listRecording = createRecording(
{ method: 'GET', url: 'https://api.example.com/users', path: '/users', timestamp: Date.now() },
{ status: 200, body: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }], timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, listRecording)
// GET /users/:id
const getRecording = createRecording(
{ method: 'GET', url: 'https://api.example.com/users/1', path: '/users/:id', timestamp: Date.now() },
{ status: 200, body: { id: 1, name: 'Alice' }, timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, getRecording)
// POST /users
const createRecording = createRecording(
{
method: 'POST',
url: 'https://api.example.com/users',
path: '/users',
body: { name: 'Charlie' },
timestamp: Date.now()
},
{ status: 201, body: { id: 3, name: 'Charlie' }, timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, createRecording)
// Use handler
const handler = createReplayHandler(scenario)
handler({ method: 'GET', path: '/users' }) // List users
handler({ method: 'GET', path: '/users/1' }) // Get user 1
handler({ method: 'POST', path: '/users' }) // Create userError Responses
// 404 Not Found
const notFoundRecording = createRecording(
{ method: 'GET', url: 'https://api.example.com/users/999', path: '/users/:id', timestamp: Date.now() },
{
status: 404,
body: {
error: 'Not Found',
message: 'User with ID 999 not found'
},
timestamp: Date.now()
}
)
// 400 Bad Request
const badRequestRecording = createRecording(
{
method: 'POST',
url: 'https://api.example.com/users',
path: '/users',
body: { email: 'invalid' },
timestamp: Date.now()
},
{
status: 400,
body: {
error: 'Bad Request',
errors: [
{ field: 'email', message: 'Invalid email format' }
]
},
timestamp: Date.now()
}
)
// 500 Internal Server Error
const serverErrorRecording = createRecording(
{ method: 'GET', url: 'https://api.example.com/error', path: '/error', timestamp: Date.now() },
{
status: 500,
body: {
error: 'Internal Server Error',
errorId: 'err_12345'
},
timestamp: Date.now()
}
)Type-Safe Recordings
interface User {
id: number
name: string
email: string
}
const recording = createRecording<User>(
{
method: 'GET',
url: 'https://api.example.com/users/1',
path: '/users/:id',
timestamp: Date.now()
},
{
status: 200,
body: {
id: 1,
name: 'John Doe',
email: 'john@example.com'
},
timestamp: Date.now()
}
)
// TypeScript knows response.body is User type
const handler = createReplayHandler(scenario)
const response = handler({ method: 'GET', path: '/users/1' })
if (response) {
const user: User = response.body // Type-safe!
}Request Matching
The replay handler matches requests using:
Method Matching
handler({ method: 'GET', path: '/users' }) // Only matches GET
handler({ method: 'POST', path: '/users' }) // Only matches POSTPath Matching
Uses @mockmaster/core for path matching:
// Exact match
handler({ method: 'GET', path: '/users' })
// Parameter match
handler({ method: 'GET', path: '/users/123' }) // Matches '/users/:id'
// Wildcard match
handler({ method: 'GET', path: '/api/anything' }) // Matches '/api/*'No Match
Returns undefined when no recording matches:
const response = handler({ method: 'GET', path: '/unknown' })
if (!response) {
console.log('No recording found')
}Integration with Tests
Vitest
import { describe, it, expect, beforeEach } from 'vitest'
import { createReplayHandler } from '@mockmaster/msw-adapter'
import { readScenario } from '@mockmaster/cli'
describe('User API', () => {
let handler: ReturnType<typeof createReplayHandler>
beforeEach(async () => {
const scenario = await readScenario('./scenarios', 'user-api')
handler = createReplayHandler(scenario)
})
it('fetches user by ID', () => {
const response = handler({ method: 'GET', path: '/users/1' })
expect(response).toBeDefined()
expect(response!.status).toBe(200)
expect(response!.body).toMatchObject({
id: 1,
name: expect.any(String)
})
})
})Jest
describe('User API', () => {
let handler: ReturnType<typeof createReplayHandler>
beforeEach(async () => {
const scenario = await readScenario('./scenarios', 'user-api')
handler = createReplayHandler(scenario)
})
test('creates new user', () => {
const response = handler({
method: 'POST',
path: '/users',
body: { name: 'New User' }
})
expect(response.status).toBe(201)
})
})Best Practices
1. Descriptive Scenario Names
// Good
createScenario('user-login-success', 'Successful user login with valid credentials')
createScenario('checkout-with-coupon', 'Complete checkout flow with discount coupon applied')
// Bad
createScenario('test1', 'test')
createScenario('scenario', 'stuff')2. Include Relevant Headers
const recording = createRecording(
{ method: 'GET', path: '/users/1', timestamp: Date.now() },
{
status: 200,
body: { id: 1, name: 'John' },
headers: {
'content-type': 'application/json',
'cache-control': 'no-cache',
'x-rate-limit-remaining': '99'
},
timestamp: Date.now()
}
)3. Use Path Parameters
// Good - uses parameter
path: '/users/:id'
// Avoid - hardcoded ID
path: '/users/123'4. Test Error Cases
// Success case
const successRecording = createRecording(...)
// Not found
const notFoundRecording = createRecording(...)
// Bad request
const badRequestRecording = createRecording(...)
// Server error
const serverErrorRecording = createRecording(...)Related Documentation
- Record & Replay Concepts
- Scenarios & Recordings
- @mockmaster/cli - Save/load scenarios
- Testing Guides - Use in tests