A little while ago Matthew posted a how-to on testing APIs like Orchestrate. It used a library called VCR to record calls made to the API and save them for later test runs. Fantastic! The only downside was that VCR exists for Ruby and Python, but not Go, which is my language of choice. This spurred me into writing DVR.

For the uninitiated, VCR/DVR are libraries that can record HTTP calls from a given test, and then use the recorded calls later when re-running the test. This makes it possible to run the tests without having to call out to the actual API, which makes testing much easier.

You can run tests in Travis on Pull Requests

Unless the API you are testing against is public, you will have to embed some kind of credentials into the invocation. If you are using Travis then this would typically come about via secure environment variables that ensure your login isn’t accessible to the world. The problem with this is that Travis can’t allow a pull request against your repo to see those variables, so they go unset, and therefore your tests may not even run against a pull request prior to merging. DVR can help with this situation by removing the private credentials from Travis altogether. Instead, it runs against the recorded requests in the DVR archive which is fast and doesn’t rely on any shared secrets.

You can run tests without network access.

Since DVR completely captures and intercepts the request, it removes the need for a network connection. This means that your tests will be able to pass, even if a backend service is having issues. You can run the tests on an airplane, and know that you won’t get a random build failure.

You can run the tests even if you do not have permission to the resource.

To run, sometimes a test requires a specific permission outside the access of a developer. For example, a call to spin up new machines in an OpenStack cluster might be something users do in production, but not from development accounts. With DVR, the interaction can be recorded once, and then the recording can be used when testing, working around the need for every developer to have an account capable of starting a new instance.

How do I start using DVR?

DVR can be use with go 1.1 or higher, and can be downloaded via the following command: go get github.com/orchestrate-io/dvr

Then create a test that uses an HTTP call. As an example this test:

package dvr_example

import (
        "fmt"
        "net/http"
        "testing"
)

func TestSimpleHttpCall(t *testing.T) {
        resp, err := http.Get("http://example.com/")
        if err != nil {
                t.Fatalf("Error from http: %s", err)
        }

        T.Logf("Date: %s\n", resp.Headers.Get("Date"))
}

Since the servers at example.com return a Date header we can use that to evaluate when the request was processed. A normal run looks like this:

$ go test -v .
=== RUN TestSimpleHttpCall
--- PASS: TestSimpleHttpCall (0.04 seconds)
        dvr_test.go:16: Date: Fri, 24 Oct 2014 18:45:56 GMT
PASS
ok      dvr_example     0.057s

Now, if we want to record we can pass arguments to the test call (note that we need to make a testdata directory first since that is where the archive file will be stored.) In this run the call will still be made to the server, however the results are saved in testdata/archive.dvr.

$ mkdir -p testdata
$ go test -v . -dvr.record
=== RUN TestSimpleHttpCall
--- PASS: TestSimpleHttpCall (0.05 seconds)
        dvr_test.go:16: Date: Fri, 24 Oct 2014 18:48:29 GMT
PASS
ok      dvr_example     0.059s

And now, we can run the test again, only this time we use the archive rather than making calls directly to the example.com. Note that the time displayed in this test is exactly the same.

$ $ go test -v . -dvr.replay
=== RUN TestSimpleHttpCall
--- PASS: TestSimpleHttpCall (0.00 seconds)
        dvr_test.go:16: Date: Fri, 24 Oct 2014 18:48:29 GMT
PASS
ok      dvr_example     0.014s

In some more complicated examples you will need to swap out an http.Client object with the RoundTripper returned from dvr.NewRoundTripper() so that the HTTP calls will be intercepted, but the simple import in the above example handles all cases that use the default HTTP client.

There are extensive godocs for the package, and it is thoroughly tested as well.

Happy coding!