msw-auto-recorder
v0.1.0
Published
Automatically record and replay network requests for testing with MSW integration
Maintainers
Readme
AutoMock
A TypeScript library that integrates with MSW (Mock Service Worker) to automatically record and replay network requests for testing. It captures real API responses during development and serves them from cache in CI/CD environments.
Features
- 🔄 Automatic Recording & Replay: Records real HTTP traffic during development, replays from cache in CI/CD
- 🌐 HTTP Support: Full support for HTTP requests with automatic caching
- 🔀 URL Replacement: Redirect requests to local test servers during recording
- ⚡ Fast Responses: 5ms delay for cached responses vs real network timing
- 🎯 MSW Integration: Works seamlessly with existing MSW setup
- 🔧 Environment Control: Toggle recording with
AUTO_MOCK_RECORDenvironment variable - 📝 Helpful Error Messages: Clear guidance when requests fail or caches are missing
- 🧩 Flexible API: Extensible API for custom handlers (WebSocket, GraphQL, etc.)
Installation
npm install msw-auto-recorder mswQuick Start
import { setupServer } from 'msw/node'
import { AutoMock } from 'msw-auto-recorder'
// Create AutoMock with optional URL replacements
const autoMock = new AutoMock('./cache.json', {
'api.production.com': 'localhost:3001' // Redirect prod API to local test server
})
// Setup MSW server with HTTP handlers
const server = setupServer(
...autoMock.getFallbackHandlers() // HTTP requests (uses flexible API internally)
)
server.listen({ onUnhandledRequest: 'bypass' })
// Set recording mode
process.env.AUTO_MOCK_RECORD = 'true' // Enable recording
// process.env.AUTO_MOCK_RECORD = 'false' // Serve from cache only (CI/CD)HTTP Requests (Automatic)
HTTP requests are automatically recorded and replayed using the flexible API internally:
// This will be recorded when AUTO_MOCK_RECORD=true
const response = await fetch('https://api.example.com/users')
// On subsequent runs with AUTO_MOCK_RECORD=false, serves from cache
const cachedResponse = await fetch('https://api.example.com/users')Flexible API for Custom Handlers
AutoMock provides a powerful flexible API that allows you to create custom handlers for any protocol (WebSocket, GraphQL, custom protocols, etc.) while still benefiting from automatic cache recording and playback.
Note: The HTTP handler itself uses this flexible API, demonstrating best practices and DRY principles.
Basic Usage
// Example: Recording any data type
if (autoMock.isRecordingEnabled()) {
// Record any data - strings, objects, arrays, etc.
autoMock.record('API', 'https://api.example.com/users', 'query-params', {
users: [{ id: 1, name: 'Alice' }],
meta: { total: 1, page: 1 }
})
// Record simple data
autoMock.record('CUSTOM', 'custom://identifier', 'input-data', 'simple response')
}
// Retrieve from cache
const userData = autoMock.getCached('API', 'https://api.example.com/users', 'query-params')
const simpleData = autoMock.getCached('CUSTOM', 'custom://identifier', 'input-data')WebSocket Example
import { ws } from 'msw'
import { WebSocket } from 'ws'
// Example: Custom WebSocket handler using AutoMock's simplified API
const customWsHandler = ws.link('ws://localhost:4000/socket').addEventListener('connection', ({ client }) => {
client.addEventListener('message', (event) => {
const message = event.data.toString()
// Check cache first
const cached = autoMock.getCached('WS', 'ws://localhost:4000/socket', message)
if (cached && !autoMock.isRecordingEnabled()) {
// Serve from cache - cached data can be any format
client.send(typeof cached === 'string' ? cached : JSON.stringify(cached))
return
}
if (autoMock.isRecordingEnabled()) {
// Recording mode: forward to real server and record response
const realWs = new WebSocket('ws://real-server.com/socket')
realWs.on('message', (response) => {
// Record the raw response or any processed format
autoMock.record('WS', 'ws://localhost:4000/socket', message, response.toString())
// Forward to client
client.send(response)
})
realWs.on('open', () => {
realWs.send(message)
})
} else {
// No cache and recording disabled
client.send('{"error": "Not found in cache"}')
}
})
})
// Add to MSW server
const server = setupServer(
...autoMock.getFallbackHandlers(),
customWsHandler
)Advanced Example: GraphQL over WebSocket
// Example: GraphQL over WebSocket handler
const graphqlWsHandler = ws.link('ws://localhost:4000/graphql').addEventListener('connection', ({ client }) => {
client.addEventListener('message', (event) => {
const message = JSON.parse(event.data.toString())
if (message.type === 'start') {
const { query, variables } = message.payload
const requestKey = JSON.stringify({ query, variables })
// Check for cached GraphQL operation
const cached = autoMock.getCached<{ requestData: any, responseData: string[] }>('GRAPHQL', 'ws://localhost:4000/graphql', requestKey)
if (cached?.customData && !autoMock.isRecordingEnabled()) {
// Serve cached GraphQL responses
const responses = cached.customData.responseData
responses.forEach((response, index) => {
setTimeout(() => {
client.send(JSON.stringify({
id: message.id,
type: 'data',
payload: JSON.parse(response)
}))
}, index * 10)
})
setTimeout(() => {
client.send(JSON.stringify({ id: message.id, type: 'complete' }))
}, responses.length * 10)
} else if (autoMock.isRecordingEnabled()) {
// Record GraphQL operation
const responses: string[] = []
const realWs = new WebSocket('ws://real-graphql-server.com/graphql', 'graphql-ws')
realWs.on('message', (data) => {
const response = JSON.parse(data.toString())
if (response.type === 'data') {
responses.push(JSON.stringify(response.payload))
} else if (response.type === 'complete') {
// Record the complete GraphQL operation
autoMock.record('GRAPHQL', 'ws://localhost:4000/graphql', requestKey, {
status: 200,
statusText: 'OK',
headers: {},
body: ''
}, {
requestData: { query, variables },
responseData: responses
})
}
client.send(data)
})
realWs.on('open', () => {
realWs.send(JSON.stringify({ type: 'connection_init' }))
realWs.send(event.data)
})
}
}
})
})Environment Variables
AUTO_MOCK_RECORD=true: Enable recording of new requestsAUTO_MOCK_RECORD=false: Serve only from cache (recommended for CI/CD)
API Reference
Constructor
new AutoMock(cachePath?, urlReplacements?)cachePath: Path to cache file (default:'./auto-mock-cache.json')urlReplacements: Object mapping URLs to redirect during recording
Core Methods
getFallbackHandlers()- Returns array of MSW HTTP handlers (using flexible API internally)clearCache()- Clear all cached requestsgetCacheStats()- Get cache statistics and debug info
Flexible API Methods
Recording
record<T>(method, url, requestData, responseData, customData?: T)Records a response to cache. Used by HTTP handler and available for custom handlers.
method: Request method (e.g., 'GET', 'POST', 'WS', 'GRAPHQL')url: Request URLrequestData: Request data to use as cache keyresponseData: Response data with status, headers, bodycustomData: Optional custom data for non-HTTP protocols (typed)
Retrieval
getCached<T>(method, url, requestData?): (CacheResponse & { customData?: T }) | nullGet cached response including both HTTP response data and optional custom data.
Utilities
isRecordingEnabled(): booleanCheck if recording is currently enabled.
URL Replacement
Redirect requests during recording:
const autoMock = new AutoMock('./cache.json', {
// Exact URL replacement
'https://api.external.com/users': 'http://localhost:8080/users',
// Domain-level replacement (preserves paths)
'external-api.com': 'localhost:3001'
})Error Handling
AutoMock provides helpful error messages:
- HTTP: When recording disabled and cache empty, suggests enabling recording
- HTTP: When remote server fails, provides URL replacement mapping suggestions
- Custom Handlers: Can implement their own error handling using the flexible API
Design Philosophy
AutoMock follows DRY (Don't Repeat Yourself) principles:
- HTTP Handler as Example: The built-in HTTP handler uses the same flexible API available to users, serving as a reference implementation
- Unified Caching: All handlers (HTTP, custom) use the same cache format and methods
- Consistent Patterns: Recording and playback follow the same patterns across all handler types
Cache Statistics
Track different types of requests:
const stats = autoMock.getCacheStats()
console.log(`Total: ${stats.totalRequests}`)
console.log(`HTTP: ${stats.httpRequests}`)
console.log(`Custom: ${stats.customRequests}`)Development
npm test # Run tests
npm run build # Build the projectMigration from GraphQL WebSocket Support
Previous versions included built-in GraphQL over WebSocket support. The new flexible API allows you to implement GraphQL WebSocket handlers with more control and customization. See examples above for implementation patterns.
License
MIT
