Go Package for testing HTTP interactions: github.com/dnaeon/go-vcr
Tags: go, testing
Published: 2021-05-09 00:44:20

Previously I covered [`github.com/h2non/gock`](https://mariocarrion.com/2021/03/09/go-package-mocking-http-request-gock.html), that package is used for testing HTTP requests, the way it works is by manually mocking both requests and responses, this time we are taking a different approach and instead of doing that we are saving both the requests and responses as fixtures for _replaying_ them in future tests.

> Please refer to the [full example code](https://github.com/MarioCarrion/videos/tree/3c7d5114731b15d73374bdb677b2aebb8cefedd0/2021/03/18-go-package-testing-http-go-vcr) for more details.

Let me introduce to you [`github.com/dnaeon/go-vcr`](https://github.com/dnaeon/go-vcr).

---

## Using dnaeon/go-vcr

The way this package works is by injecting as a `http.RoundTripper` assigned to the `Transport` field in the used `http.Client`, [for example](https://github.com/MarioCarrion/videos/blob/3c7d5114731b15d73374bdb677b2aebb8cefedd0/2021/03/18-go-package-testing-http-go-vcr/main_test.go#L103-L109):

```go
// recorder is "github.com/dnaeon/go-vcr/recorder"
r, err := recorder.New(path.Join("fixtures", "weather_401"))
if err != nil {
	t.Fatalf("failed creating recorder %s", err)
}

return &http.Client{Transport: r}, r.Stop
```

The first time this code is run it would create a new file called `fixtures/weather_401.yaml`, if it does not exist, saving all the information related to _requests_, including things such as query arguments and headers, as well as the _responses_ received after.

The idea behind saving this file is to have a deterministic way to get the same same results when running the same tests in the future. `dnaeon/go-vcr` behind the scenes uses that file to properly match the request and return the responses that were previously saved.

## Removing secrets

There are cases where we are actually using real credentials, either as HTTP headers or query arguments, `dnaeon/go-vcr` allows you to remove those so we don't leak them, [for example](https://github.com/MarioCarrion/videos/blob/3c7d5114731b15d73374bdb677b2aebb8cefedd0/2021/03/18-go-package-testing-http-go-vcr/main_test.go#L48-L79):

```go
r, err := recorder.New(path.Join("fixtures", "weather_200"))
if err != nil {
		t.Fatalf("failed creating recorder %s", err)
}

cleanURL := func(u *url.URL) *url.URL {
	q := u.Query()
	q.Del("appid")

	u.RawQuery = q.Encode()

	return u
}

r.AddFilter(func(i *cassette.Interaction) error {
	u, err := url.Parse(i.Request.URL)
	if err != nil {
		return err
	}

	i.URL = cleanURL(u).String()

	return nil
})

r.SetMatcher(func(r *http.Request, i cassette.Request) bool {
	r.URL = cleanURL(r.URL)

	return cassette.DefaultMatcher(r, i)
})

return &http.Client{Transport: r}, r.Stop
```

In the case above we are both:

* Removing a query argument in the request, **and**
* Updating the matcher to not consider that value.

By doing that we are able to deterministically run our tests afterwards.

## Final thoughts

`dnaeon/go-vcr` and `h2non/gock` complement each other, `go-vcr` is useful when working with third party APIs where only a few fields are used, it allows _future us_ to refer to the saved fixtures to make decisions regarding things like adding support for using already known fields or make decisions about the possible options available on the third party API.

In the end `dnaeon/go-vcr` is a must, **highly recommended**.