Beginner · 10 min
Record traffic, replay it offline
Capture a real exchange once and turn it into a deterministic fixture.
You’ll buildA committed cassette that replays the real payload with the network taken away.
1
Step 1
Wrap the scope in a cassette
The first run performs the real request and writes the file. Every run after that replays it, so the test is offline and deterministic.
import NovaNetworkCassette
try await withCassette(at: fixtureURL, upstream: Transport()) { transport in
let client = NetworkClient(transport: transport)
let user: User = try await client.load(
request: request,
authScope: nil
)
#expect(user.name == "Ada")
}2
Step 2
Check what was written
Bodies are stored as text, keys are sorted, and there are no timestamps, so the file reads like the payload and a real change shows up as a real diff.
{
"interactions" : [
{
"request" : {
"headers" : { "Authorization" : "<redacted>" },
"method" : "GET",
"url" : "https://api.example.com/users/1"
},
"response" : {
"body" : { "text" : "{\"id\":1,\"name\":\"Ada\"}" },
"status" : 200
}
}
],
"version" : 1
}3
Step 3
Tighten matching only where it matters
Method and full URL match by default, query order ignored. Headers and bodies stay out unless you ask, because a nonce or trace id would break every replay invisibly.
// A cache-busting parameter you do not want to match on:
CassetteMatchRule.methodAndPath
// A search endpoint that varies by payload:
CassetteMatchRule.includingBody
// Content negotiation that really is part of the identity:
CassetteMatchRule.default.matchingHeaders("Accept-Language")4
Step 4
Replay a sequence, not a constant
Repeated requests replay as episodes, in recorded order, so a polling or pagination flow stays a flow.
let transport = CassetteTransport(
mode: .replay,
cassette: try Cassette.load(from: url),
repeatPolicy: .repeatLast
)Tutorial complete