Replay Brings Battle-Tested HTTP Recording to Swift Development

The year is 2025, and Swift developers building networked applications face a persistent challenge: ensuring the reliability and speed of their automated tests. The traditional approaches to testing networking code often fall short, leading to slow, brittle test suites that are susceptible to the vagaries of live third-party services. While stubbing URLSession can offer a degree of isolation, it necessitates the maintenance of duplicate networking logic. Manually curating JSON fixtures, though a common practice, is prone to becoming outdated, leaving tests susceptible to unexpected failures without immediate indication. A more robust and efficient methodology, long established in other programming languages, is now accessible to the Swift ecosystem: recording real HTTP traffic once and replaying it instantly for every subsequent test run. This principle is embodied by the newly released Replay library, which brings this proven pattern to Swift development.
The genesis of this approach can be traced back to February 2010, when Myron Marston introduced VCR for Ruby. Drawing a parallel to the functionality of a videocassette recorder, VCR enabled developers to capture HTTP interactions, thereby allowing tests to replay these interactions without needing to establish live network connections. The mantra was simple and effective: "Record once, play back forever."
This innovative concept rapidly gained traction and inspired similar implementations across diverse programming languages. Python saw the emergence of VCR.py and pytest-recording, extending the VCR paradigm to its developers. The home video theme continued with Java’s Betamax, while Go adopted its own version with go-vcr. For years, developers like the author of this article had utilized these tools in their respective environments, often expressing a desire for a comparable solution within the Swift community. While Venmo’s DVR project offered a partial solution, leveraging the same URLProtocol injection point as Replay, it was developed in an earlier era of Swift, before the advent of modern tooling that could facilitate a truly seamless and intuitive experience.
A Nod to the Past, a Leap to the Future
The development of Replay is deeply informed by its predecessors. A key question guiding its creation was, "What has changed to allow for a superior implementation today?" Two significant advancements stand out.
Firstly, the HTTP Archive (HAR) format has solidified its position as a de facto standard for archiving HTTP traffic. When Myron Marston initially developed VCR, a widely adopted format for such archives did not exist, leading him to create a custom YAML-based solution, which inspired the "cassette" terminology. However, around the same period, Jan Odvarko, a member of the Firefox developer tools team, was instrumental in developing the HAR format. Today, HAR files are universally supported, exported by all major web browsers and widely adopted by network debugging tools such as Charles Proxy, Proxyman, mitmproxy, and Postman. Replay’s decision to utilize HAR streamlines the process of integrating real-world network traffic into tests. Developers can now capture traffic directly from browser developer tools and seamlessly incorporate it into their test fixtures, which can be easily inspected and edited with any standard text editor.
Secondly, Swift has evolved to provide the necessary extension points for building sophisticated testing tools. Swift’s testing framework, particularly the TestScoping protocol introduced in Swift 6.1, enables declarative, per-test configuration that mirrors the capabilities of pytest fixtures. Furthermore, the integration of Swift Package Manager plugins allows for the development of tooling that feels intrinsically native to the Swift development environment. These advancements, which were not readily available in a convenient or intuitive form previously, have paved the way for Replay’s architecture.
The Mechanics of Replay: Seamless Integration
Replay operates by integrating seamlessly with Swift’s testing framework. By applying the .replay trait to a test case, Replay intercepts HTTP requests and serves responses directly from a HAR file, effectively bypassing the need to interact with the live network.
Consider the following example of a Swift test:
import Testing
import Replay
struct User: Codable
let id: Int
let name: String
let email: String
@Test(.replay("fetchUser"))
func fetchUser() async throws
let (data, _) = try await URLSession.shared.data(
from: URL(string: "https://api.example.com/users/42")!
)
let user = try JSONDecoder().decode(User.self, from: data)
#expect(user.id == 42)
In this code snippet, the .replay("fetchUser") trait instructs Replay to load responses from a file named Replays/fetchUser.har. The application’s production code continues to use URLSession as usual, eliminating the need for custom protocols or injected mocks. Replay’s interception mechanism leverages the built-in affordances of the Foundation URL Loading System, ensuring compatibility with URLSession.shared, custom URLSession instances, and popular networking libraries like Alamofire.
The Recording Workflow: A Deliberate Process
The initial execution of a test marked with .replay is intentionally designed to fail. This deliberate failure is a crucial security feature, preventing the accidental recording of sensitive information such as credentials, Personally Identifiable Information (PII), or session tokens.
Upon the first run, a test might produce output similar to this:
➜ Example_Tests.swift Test fetchUser() recorded an issue at Example_Tests.swift
--------------------------------------------------------------------------
No Matching Entry in Archive
--------------------------------------------------------------------------
Request: GET https://api.example.com/users/42
Archive: /path/to/.../Replays/fetchUser.har
This request was not found in the replay archive.
Options:
1. Run against the live network:
REPLAY_PLAYBACK_MODE=live swift test --filter <test-name>
2. Record the archive:
REPLAY_RECORD_MODE=once swift test --filter <test-name>
This "failure" serves as an explicit prompt for the developer to initiate the recording process. By requiring explicit opt-in for recording, Replay ensures that developers are always aware when network traffic is being captured.
To proceed with recording, the following command is executed:
REPLAY_RECORD_MODE=once swift test --filter fetchUser
This command directs Replay to hit the actual API endpoint, capture the resulting response, and save it to the Replays/fetchUser.har file. Subsequently, all test runs for fetchUser will execute instantaneously by replaying this recorded fixture. The efficiency gains are palpable, transforming test suites that once took minutes to complete into operations that finish in mere seconds.
The Structure of a HAR File: Transparency and Interoperability
A HAR file is fundamentally a JSON document, making it human-readable and easily inspectable. This transparency is a cornerstone of Replay’s design, fostering trust and simplifying debugging. A typical HAR file structure includes:
"log":
"version": "1.2",
"creator": "name": "Replay", "version": "1.0" ,
"entries": [
"request":
"method": "GET",
"url": "https://api.example.com/users/42",
"headers": [ "name": "Accept", "value": "application/json" ]
,
"response":
"status": 200,
"content":
"mimeType": "application/json",
"text": ""id":42,"name":"Alice""
]
This JSON format is not only human-readable but also compatible with a broad ecosystem of tools, further enhancing its utility. The inclusion of fields like version, creator (detailing the tool used for generation), and entries (representing individual request-response pairs) provides a comprehensive record of network interactions.
Managing Sensitive Data: Enhanced Security with Filters
HAR files, by their nature, can inadvertently capture sensitive information embedded within request headers or query parameters, such as session cookies, authorization tokens, and API keys. Replay addresses this potential vulnerability through its robust filtering capabilities, allowing developers to redact sensitive data during the recording process.
This is achieved by configuring filters directly within the @Test annotation:
@Test(
.replay(
"fetchUser",
filters: [
.headers(removing: ["Authorization", "Cookie"]),
.queryParameters(removing: ["token", "api_key"])
]
)
)
func fetchUser() async throws /* ... */
The guiding principle here is to establish filters before recording commences, ensuring that sensitive data is never captured in the first place. This proactive approach to data security is a critical component of Replay’s design.
Flexible Matching Strategies: Adapting to Dynamic APIs
By default, Replay matches incoming HTTP requests against recorded entries based on the HTTP method and the complete URL. However, for APIs that incorporate dynamic query parameters, such as pagination cursors, timestamps, or cache-busting tokens, this strict matching can lead to test failures. To accommodate such scenarios, Replay offers flexible matching strategies.
Developers can configure looser matching criteria, for instance, by focusing on the HTTP method and the URL path:
@Test(.replay("fetchUser", matching: [.method, .path]))
func fetchUser() async throws /* ... */
Replay provides a comprehensive set of matchers, including .method, .url, .host, .path, .query, .headers([...]), .body, and a .custom(...) option for implementing arbitrary matching logic. This flexibility ensures that Replay can adapt to a wide array of API structures and behaviors.
Inline Stubs: Targeted Test Scenarios
Beyond recording real network traffic, Replay also supports inline stubbing for specific test cases. This feature is particularly useful for simulating error conditions, testing edge cases, or scenarios where the exact response content is less critical than the status code or headers.
An example of inline stubbing:
@Test(
.replay(
stubs: [
.get("https://api.example.com/health", 200, ["Content-Type": "application/json"])
#""status": "ok""#
]
)
)
func testHealthCheck() async throws
let (data, _) = try await URLSession.shared.data(
from: URL(string: "https://api.example.com/health")!
)
#expect(String(data: data, encoding: .utf8) == #""status": "ok""#)
This capability allows developers to precisely control the simulated network responses, facilitating more targeted and comprehensive testing of application logic.
A Maturation of Testing Practices for Swift
The refinement of HTTP recording practices over fifteen years across various programming language ecosystems—including Ruby, Python, and JavaScript—has established a set of clear best practices. These include the imperative to capture real network traffic, diligently strip sensitive data, implement mechanisms for failing fast when fixtures are absent, and provide informative error messages when issues arise. Replay synthesizes these well-honed principles and delivers them to the Swift development community.
For developers who have grappled with the unreliability of API tests or have postponed the essential task of testing their network code due to perceived high overhead, Replay presents a compelling solution. By embracing this tool, developers can significantly enhance the stability, speed, and maintainability of their Swift applications.
Replay is available as an open-source project on GitHub. Developers are encouraged to explore its capabilities and provide feedback.







