HTTP unit tests using ExVCR
Request/response recorder for rapid test-driven development when accessing external web services.
Elixir
What is ExVCR?
Record and replay HTTP interactions library for elixir. It’s inspired by Ruby’s VCR, and trying to provide similar functionalities.
ExVCR allows you to automatically record any HTTP request/response to a JSON text file. Subsequent requests matching the same URL are served up by the cached response from disk. The feedback loop is minimised when following a test-driven development approach due to the instant HTTP responses. The recorded fixtures support offline development, help other developers, and allow test execution without external HTTP requests in continuous integration environments.
Motivation
I authored an Elixir wrapper for the Strava API. During the development I wrote unit tests to build the API and verify functionality. Most of these tests made HTTP calls to the Strava API.
Strava enforces a rate limit to its REST API. The default rate limit allows 600 requests every 15 minutes, with up to 30,000 requests per day. I also use the mix test.watch task to execute the test suite after any file is saved. The rate limit and frequent test runs meant that ExVCR was an ideal fit.
The Strava library uses HTTPoison as the HTTP client. This uses hackney to execute the HTTP requests. Therefore I had to use the ExVCR.Adapter.Hackney
adapter in my tests.
Usage
Using ExVCR in a unit test requires the following code changes.
1. Add exvcr
to the project’s dependencies in config/mix.exs
as a test-only dependency.
2. Disable ExUnit’s async support.
3. Use the ExVCR mock macro with the ExVCR.Adapter.Hackney
adapter.
4. Configure HTTPoison
to start for all tests.
5. Wrap the lines of code making the HTTP request inside a use_cassette
block.
The string provided to use_cassette
is used to build the path to the recorded JSON file, relative to fixture/vcr_cassettes
. So you can use the path separator character to group related fixtures together in the same directory.
Sample test
The full code for the sample test is given below.
Running tests
The first time you run the test there will be no ExVCR fixture data. So HTTP requests are made and the response is cached to disk at fixture/vcr_cassettes
. Subsequent test runs will use the cached fixture data if it matches the requested URL.
For the example test above, the cached fixture JSON data is given below.
To execute the unit tests against the real web services you simply delete the stored fixture files.
Query string parameters
By default, query params are not used for matching with URLS recorded in ExVCR fixtures. You must specify match_requests_on: [:query]
in order to include query params.
Filtering sensitive content
The Strava API uses an access key included in the HTTP Authorization
request header to authenticate requests. As I wanted to include the recorded fixture data in the public Git repository I had to remove this header. This is supported by the ExVCR config setting filter_sensitive_data
added to config/test.exs
. As shown below, I replace the Bearer token containing the access key with a placeholder value using a regular expression.
The config blacklists the Set-Cookie
and X-Request-Id
response headers to ensure that these are also not recorded to prevent revealing sensitive data.
Continuous integration
Including fixture data in source control allowed me to run the test suite on Travis CI.
Without using ExVCR, the tests would require a valid access key to use the Strava API. I would have to register a new Strava developer account, since each account only has a single key, and then use an environment variable to configure this setting. With recorded HTTP fixture data included in source control the tests suite runs quickly without being affected by external service availability.
In the config/test.exs
mix configuration file I optionally include a config/test.secret.exs
file, if present. This file contains the private Strava API settings including my own access key. The file is excluded from source control by adding /config/test.secret.exs
to the .gitignore
file.
This approach also supports other developers who might want to contribute changes without requiring their own Strava API access key. They can clone the Git repository and immediately run the full test suite.
Conclusion
Running the Strava test suite without recorded HTTP fixture data.
$ mix test
....................
Finished in 43.5 seconds
21 tests, 0 failures, 1 skipped
Now running the same tests with fixture data present results in a significant speed up.
$ mix test
....................
Finished in 4.1 seconds
21 tests, 0 failures, 1 skipped
The total time reduced by a factor of ten: from 44 seconds down to 4 seconds.