undici
undici copied to clipboard
feat: add SnapshotAgent for HTTP request recording and playback
Summary
This PR implements a new SnapshotAgent that extends MockAgent to provide automatic recording and playback of HTTP requests for testing purposes. This addresses the feature request in #4114 for "record and play back requests" functionality.
Features
- Record Mode: Captures real HTTP requests and responses to snapshot files
- Playback Mode: Replays recorded interactions without making real network calls
- Update Mode: Uses existing snapshots when available, records new ones when missing
- Full undici compatibility: Works with
request(),fetch(),stream(),pipeline(), etc. - Base64 response storage: Consistent serialization of all response body types
- TypeScript support: Complete type definitions and comprehensive tests
API Usage
Recording Real Requests
import { SnapshotAgent, setGlobalDispatcher } from 'undici'
const agent = new SnapshotAgent({
mode: 'record',
snapshotPath: './test/snapshots/api-calls.json'
})
setGlobalDispatcher(agent)
// Makes real requests and records them
await fetch('https://api.example.com/users')
await agent.saveSnapshots()
Replaying in Tests
const agent = new SnapshotAgent({
mode: 'playback',
snapshotPath: './test/snapshots/api-calls.json'
})
setGlobalDispatcher(agent)
// Uses recorded response instead of real request
const response = await fetch('https://api.example.com/users')
Test plan
- [x] Comprehensive unit tests for
SnapshotRecorderutility class - [x] Integration tests for
SnapshotAgentin all three modes (record/playback/update) - [x] TypeScript definition tests using
tsd - [x] Example code with real API integration patterns
- [x] POST request handling with request bodies
- [x] Error handling for missing snapshots
- [x] File format validation and persistence
🤖 Generated with Claude Code
cc @GeoffreyBooth let me know what you think
This is a great addition, thank you for doing this! A few things come to mind reading the documentation:
- How can the matching be customized? For example, to match on some headers but not all, to ignore authentication tokens for example. In my app I had subsequent POST calls to the same API where everything matched except the body, so I needed to include that as part of the matching criteria.
- How are subsequent identical calls handled? One feature of nock is that you can define responses like “the first time, return X, the second time, return Y”.
- How can I update existing mocks? I see the “update mode” example but that seems to be an additive operation to add new mocks to an existing set; is there a way to fully replace an existing set?
Maybe some of these are appropriate to handle as follow-up enhancements. I’ll try to get something together soon that’s a minimal reproduction of what I’ve been working with.
How can the matching be customized? For example, to match on some headers but not all, to ignore authentication tokens for example. In my app I had subsequent POST calls to the same API where everything matched except the body, so I needed to include that as part of the matching criteria.
This can be a pretty interesting enhancement. Especially for request that requires some level of security, often those ones are good candidates to be excluded from the snapshot itself.
Having a way to state wether or not record a given request/response (either at the dispatch level of agent instantiation) will bring these control benefits.
From my end (and as possible enhancement) I'd like to explore the integration with the Mocks feature. As replaying these request under testing environments can be beneficial, especially if custom mocks are already set. I don't believe they should be mutually exclusive but compatible between each other.
@Uzlopak @metcoder95 could you take another look?
@metcoder95 I've found a bug on the compose/interceptor handling, I'm working on it.
@metcoder95 it’s done.
@metcoder95 removed.
@GeoffreyBooth ptal