Core ConceptsRecord & Replay

Record & Replay

The record and replay pattern is at the heart of MockMaster. It allows you to capture real API responses and replay them deterministically in your tests.


Overview

The record and replay pattern enables you to:

  • Record real API responses during development or from production
  • Store them as JSON files in your repository
  • Replay them in tests for deterministic, fast testing

This approach ensures your tests are:

  • Fast - No network calls required
  • Reliable - Same responses every time
  • Offline-capable - Work without internet
  • Version-controlled - Scenarios are committed with your code

Creating Recordings

A recording captures a single HTTP request and its corresponding response:

import { createRecording } from '@mockmaster/msw-adapter'
 
const recording = createRecording(
  // Request
  {
    method: 'GET',
    url: 'https://api.example.com/users/123',
    path: '/users/123',
    timestamp: Date.now()
  },
  // Response
  {
    status: 200,
    body: {
      id: 123,
      name: 'John Doe',
      email: 'john@example.com'
    },
    headers: {
      'content-type': 'application/json'
    },
    timestamp: Date.now()
  }
)

Recording Structure

A recording consists of two parts:

Request:

  • method - HTTP method (GET, POST, PUT, etc.)
  • url - Full URL of the request
  • path - Path portion of the URL
  • timestamp - When the request was made
  • body (optional) - Request body
  • headers (optional) - Request headers

Response:

  • status - HTTP status code
  • body - Response body
  • headers (optional) - Response headers
  • timestamp - When the response was received

Creating Replay Handlers

Once you have recordings, create a replay handler to use them in tests:

import {
  createScenario,
  addRecordingToScenario,
  createReplayHandler
} from '@mockmaster/msw-adapter'
 
// Build scenario with recordings
let scenario = createScenario('user-api', 'User API responses')
scenario = addRecordingToScenario(scenario, recording)
 
// Create handler
const handler = createReplayHandler(scenario)
 
// Replay in tests
const response = handler({
  method: 'GET',
  path: '/users/123'
})
 
console.log(response.status) // 200
console.log(response.body)   // { id: 123, name: 'John Doe', ... }

Request Matching

The replay handler automatically matches incoming requests to recorded responses using:

Exact Method Matching

// Will match
handler({ method: 'GET', path: '/users' })
 
// Won't match (different method)
handler({ method: 'POST', path: '/users' })

Path Matching

Supports exact paths, parameters, and wildcards:

// Exact path
handler({ method: 'GET', path: '/users' })
 
// Path with parameters
handler({ method: 'GET', path: '/users/123' })
 
// Wildcard
handler({ method: 'GET', path: '/api/anything/here' })

No Match Found

If no recording matches the request, the handler returns undefined:

const response = handler({ method: 'GET', path: '/not-recorded' })
 
if (!response) {
  console.log('No recording found for this request')
}

Multiple Recordings

A scenario can contain multiple recordings for different endpoints:

import { createScenario, addRecordingToScenario } from '@mockmaster/msw-adapter'
 
let scenario = createScenario('user-api', 'Complete user API')
 
// Add GET /users
const listUsersRecording = 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, listUsersRecording)
 
// Add GET /users/:id
const getUserRecording = createRecording(
  { method: 'GET', url: 'https://api.example.com/users/1', path: '/users/:id', timestamp: Date.now() },
  { status: 200, body: { id: 1, name: 'Alice', email: 'alice@example.com' }, timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, getUserRecording)
 
// Add POST /users
const createUserRecording = createRecording(
  {
    method: 'POST',
    url: 'https://api.example.com/users',
    path: '/users',
    body: { name: 'Charlie', email: 'charlie@example.com' },
    timestamp: Date.now()
  },
  { status: 201, body: { id: 3, name: 'Charlie', email: 'charlie@example.com' }, timestamp: Date.now() }
)
scenario = addRecordingToScenario(scenario, createUserRecording)
 
// Use all recordings
const handler = createReplayHandler(scenario)
 
handler({ method: 'GET', path: '/users' })      // Returns list
handler({ method: 'GET', path: '/users/1' })    // Returns Alice
handler({ method: 'POST', path: '/users' })     // Returns new user

Type Safety

MockMaster supports TypeScript generics for type-safe recordings:

interface User {
  id: number
  name: string
  email: string
}
 
const recording = createRecording<User>(
  {
    method: 'GET',
    url: 'https://api.example.com/users/123',
    path: '/users/:id',
    timestamp: Date.now()
  },
  {
    status: 200,
    body: {
      id: 123,
      name: 'John Doe',
      email: 'john@example.com'
    },
    timestamp: Date.now()
  }
)
 
// TypeScript knows response.body is of type User
const handler = createReplayHandler(scenario)
const response = handler({ method: 'GET', path: '/users/123' })
if (response) {
  const user: User = response.body // Type-safe!
}

Best Practices

1. Record Real Responses

When possible, record actual API responses from your backend or external APIs. This ensures your tests match real-world behavior.

2. Commit Recordings

Store recordings in your repository so all developers and CI/CD pipelines use the same data.

3. Organize by Feature

Create separate scenarios for different features or user flows:

scenarios/
├── auth/
│   ├── login-success.json
│   ├── login-failure.json
│   └── logout.json
├── users/
│   ├── list-users.json
│   └── get-user.json
└── products/
    └── list-products.json

4. Update When APIs Change

When your API changes, update the recordings to match. This keeps your tests in sync with reality.

5. Test Error Cases

Don’t just record success cases. Record errors too:

// 404 Not Found
const notFoundRecording = createRecording(
  { method: 'GET', url: 'https://api.example.com/users/999', path: '/users/999', timestamp: Date.now() },
  {
    status: 404,
    body: { error: 'User not found', resourceId: '999' },
    timestamp: Date.now()
  }
)
 
// 500 Internal Server Error
const serverErrorRecording = createRecording(
  { method: 'GET', url: 'https://api.example.com/data', path: '/data', timestamp: Date.now() },
  {
    status: 500,
    body: { error: 'Internal server error', errorId: 'err_123' },
    timestamp: Date.now()
  }
)

Next Steps