Library / SDK
dnaeon/go-vcr avatar
dnaeon/go-vcr

dnaeon/go-vcr for deterministic Go HTTP tests

Record and replay your HTTP interactions for fast, deterministic and accurate tests

1,397 stars86 forksGoBSD-2-Clause

At a glance

What is it?
dnaeon/go-vcr records HTTP requests and responses in YAML cassettes and replays them during later Go tests. It fits developers testing HTTP clients who want repeatable runs without depending on a live service.
Who is it for?
Adopt dnaeon/go-vcr when repeated HTTP calls would make Go tests slow or dependent on a remote service. Do not adopt it for arbitrary dynamic server simulation.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 32 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why go-vcr addresses external calls in Go tests

dnaeon/go-vcr addresses a specific problem: Go code under test makes HTTP requests, but the test does not need a new request to a live server every time it runs. The library records an HTTP interaction during the first run and replays the stored exchange on later runs. Its README describes this approach as providing fast, deterministic and accurate tests.

The library is aimed at Go developers testing clients, API integrations and components that communicate over HTTP. A cassette can insulate those tests from network latency and changes in a remote response after the fixture has been created. It is useful when the test cares about how its code handles a known exchange, not about current server availability or a continuously changing data set.

go-vcr is not presented as a general HTTP mock server. It records interactions obtained through an HTTP client and replays them later. That boundary makes it more suitable for stable integration-style fixtures than for tests that need to generate unlimited responses, coordinate multiple services or inspect low-level network behavior. The repository includes examples for simple use, HTTPS, JSON, middleware and debugging, which shows its primary emphasis is client traffic rather than protocol simulation.

How recorder, cassette and HTTP client data flow

The main entry point is `recorder.New`, which accepts a cassette path. The test obtains an HTTP client through `r.GetDefaultClient()` and makes its request through that client. On the first run, go-vcr captures the request and response, adds the interaction to the in-memory cassette and writes it to disk as YAML.

The README's example uses a path under `testdata` derived from the test name. For `TestHelloWorld`, the first execution creates `testdata/TestHelloWorld.yaml`. The cassette contains the interaction between the test's HTTP client and the remote server. A later execution matches the incoming request against the cassette and returns the recorded response without making the corresponding external call.

The recorder must be stopped after the test. The documented example registers `r.Stop()` with `t.Cleanup`, which ensures finalization occurs even when the test function exits through `t.Fatal` or `t.Errorf`. This cleanup step matters because the recorder has state and may need to finish cassette persistence.

Request selection is handled by a matcher. The default implementation is exposed as `cassette.DefaultMatcher`, while `recorder.WithMatcher` accepts a `recorder.MatcherFunc` for project-specific behavior. Hooks also participate in the lifecycle at documented points such as `AfterCaptureHook`, `BeforeSaveHook`, `BeforeResponseReplayHook` and `OnRecorderStop`.

Based on the documented API, application code still uses a normal Go HTTP client. The recorder changes what happens beneath that client: captured traffic can pass to the network and enter a cassette during recording, or come from the cassette during replay. The README does not document cassette concurrency behavior, expiration semantics or a repair procedure for malformed YAML.

Installing go-vcr and recording a first cassette

The README gives this command for adding the v4 package to a Go module:

bash
$ go get -v gopkg.in/dnaeon/go-vcr.v4

The repository's `go.mod` declares `go 1.24`, so the project under test must meet that stated Go version requirement. The README also warns that migration from a previous go-vcr version may require recreating existing test cassettes.

A first test can create a recorder, register its cleanup, get the default client and issue a request. Keep the response body close operation in the test so the underlying HTTP body is released.

go
func TestHelloWorld(t *testing.T) {
	r, err := recorder.New(filepath.Join("testdata", strings.ReplaceAll(t.Name(), "/", "_")))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		if err := r.Stop(); err != nil {
			t.Error(err)
		}
	})

	client := r.GetDefaultClient()
	url := "https://go.dev/VERSION?m=text"
	resp, err := client.Get(url)
	if err != nil {
		t.Fatalf("Failed to get url %s: %s", url, err)
	}
	resp.Body.Close()
}

On the first execution, the README says to expect a cassette at `testdata/TestHelloWorld.yaml`. Running the test again should replay the stored interaction. The README points to `examples` for ready-to-run cases and to `pkg/recorder/recorder_test.go` for additional examples. It does not provide a separate command for listing or validating cassettes.

Matching request variants during cassette replay

Replay depends on associating a new request with the correct recorded request and response. go-vcr provides `cassette.DefaultMatcher` for the standard behavior and `recorder.WithMatcher` for tests that need different rules. The documented custom matcher compares the HTTP method, URL and request body.

A request with a body requires particular care. The example reads the body with `io.ReadAll`, closes it and replaces it with `io.NopCloser(bytes.NewBuffer(reqBody))`. Restoring the body prevents the matcher from consuming data that the code under test may still need. The matcher then compares `r.Method`, `r.URL.String()` and the body string with the recorded fields.

The choice of matcher is part of the test contract. If a suite changes headers, query values, cookies or request payloads, it should determine which of those differences identify a distinct interaction. A matcher that ignores too much can return the wrong cassette entry. A matcher that compares unstable fields can fail to find an otherwise valid entry.

The README supplies no documented matcher based on response sequence numbers, host-specific rules or application-defined headers. It also does not explain all error messages produced by an unmatched request. The package tests therefore deserve consultation when a project needs matching behavior beyond the two documented examples.

Cassettes should be recorded using the request shapes emitted by the actual test suite. If a test changes a payload after the cassette was created, the expected result depends on the selected matcher. This is a common source of false replay successes when matching is too broad and false replay failures when it is too strict.

Hooks for redacting captured HTTP data

go-vcr hooks are functions that receive HTTP interactions at different stages of recording and playback. The README identifies four supported hook kinds: `AfterCaptureHook`, `BeforeSaveHook`, `BeforeResponseReplayHook` and `OnRecorderStop`. They can modify captured data, perform logging or apply other processing at those points.

The security-sensitive example removes an `Authorization` header from captured requests immediately after capture:

go
hook := func(i *cassette.Interaction) error {
	delete(i.Request.Headers, "Authorization")
	return nil
}

r, err := recorder.New(
	filepath.Join("testdata", strings.ReplaceAll(t.Name(), "/", "_")),
	recorder.WithHook(hook, recorder.AfterCaptureHook),
	recorder.WithMatcher(cassette.NewDefaultMatcher(cassette.WithIgnoreAuthorization())),
)

The matcher option is also significant. Removing the header prevents it from being written, but it could also prevent a recorded request from matching a replay request if the matcher still compared that field. `cassette.WithIgnoreAuthorization()` tells the documented matcher construction to ignore the authorization header during matching.

Hook timing affects test behavior. The README notes that an `AfterCaptureHook` runs after an interaction has been added to the in-memory cassette. An edit made at that point can therefore affect code that later inspects the captured interaction. `BeforeSaveHook` is intended for changes immediately before persistence, while `BeforeResponseReplayHook` applies before the recorded response is returned to the client. The README does not show a complete ordering example for all hook types.

The authorization example does not establish that other credentials are removed automatically. Tokens can also appear in URLs, request bodies, response bodies or custom headers. Any cassette should be inspected for those values. The hook API provides the mechanism, but the redaction rules remain specific to the application's traffic.

YAML output, limitations and local test servers

Cassettes use YAML and default to `yaml.Marshal`. Projects can replace that function with `recorder.WithMarshalFunc`. The README demonstrates a custom function using `go.yaml.in/yaml/v4`, enabling compact sequence indentation and setting indentation to four spaces. This changes how the cassette is serialized, but the documented example does not say that it changes HTTP request matching.

A primary limitation is the fixture model itself. go-vcr replays interactions that have been recorded. It is the wrong tool when a test needs to generate a large number of arbitrary responses, simulate detailed server state or produce responses that cannot be represented as a previously captured exchange. It also leaves the test author responsible for deciding what request and response fields may be stored.

A local `httptest.Server` with handlers is a practical alternative because it generates responses at runtime. Where go-vcr stores a captured exchange in YAML and selects it by matching, a handler receives a live server request and returns code-defined status, headers and body. A local server offers direct control over synthetic behavior and can cover cases that were never observed in a recording. The cost shifts from cassette maintenance to handler and fixture logic in each test.

Neither approach should be selected solely from the label “HTTP testing.” go-vcr fits known exchange replay. A local server fits dynamic service behavior. If the production code requires a particular external protocol that the cassette cannot represent, the local-server approach is the more direct choice.

Maintenance, upgrades and cassette licensing concerns

The repository is not archived, and its last push was on 2026-09-21. Listed releases include `v4.0.7` on 2026-06-25, `v4.0.6` on 2025-11-25 and `v4.0.5` on 2025-08-17. These dates show recent repository activity, but the README does not define a long-term support window or compatibility promise for cassette formats.

Upgrade cost is documented at a basic level. The README states that users migrating from a previous version may need to recreate test cassettes. A version change can therefore affect fixture files as well as Go source imports. Teams should review the `CHANGELOG.org` file and run the test suite with regenerated or reviewed cassettes after a major upgrade. The project includes a `Makefile` with commands for downloading dependencies and running tests, including race detection:

bash
$ make test

That command runs the repository's documented `go test -v -race ./...` target. The README does not prescribe a separate cassette migration command.

The repository declares the `BSD-2-Clause` license. That is the project's license designation, not legal advice and not a decision about whether recorded third-party data may be distributed. Cassettes may contain credentials, access tokens, customer records or copyrighted response content. Teams should inspect fixture files before committing or publishing them and ensure their repository and data policies permit storage.

The recent push and release dates are useful maintenance facts, but upgrade review remains fixture-specific. The first project-specific verification is whether the current tests can replay existing cassettes after the intended version change, followed by a review of every newly recorded file.

Editorial conclusion

Adopt dnaeon/go-vcr when repeated HTTP calls would make Go tests slow or dependent on a remote service. Do not adopt it for arbitrary dynamic server simulation. Before use, verify that cassette matching covers the request variations your tests create and that recorded data contains no credentials or private information.

Frequently asked questions

How do I install dnaeon/go-vcr?

The README instructs users to run `go get -v gopkg.in/dnaeon/go-vcr.v4`. The repository's `go.mod` declares Go 1.24.

How does dnaeon/go-vcr replay HTTP requests?

The first run records an HTTP interaction into a YAML cassette. Later runs match incoming requests against recorded request and response pairs and replay the stored interaction instead of making the external call.

Can dnaeon/go-vcr redact authorization headers?

Yes. The README shows an `AfterCaptureHook` that deletes the `Authorization` header and a matcher configured with `cassette.WithIgnoreAuthorization()`.

Official sources

  1. dnaeon/go-vcr on GitHub
  2. Issues
  3. License: BSD-2-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dnaeon-go-vcr.svg)](https://hysenlabs.com/projects/dnaeon-go-vcr)

Was this useful?