rss resume / curriculum vitae linkedin linkedin gitlab github twitter mastodon instagram
Go Package for testing HTTP interactions: github.com/dnaeon/go-vcr
Mar 28, 2021

Previously I covered github.com/h2non/gock, 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 for more details.

Let me introduce to you 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:

// 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:

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.

Conclusion

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.


Back to posts