NC

Mocking Web Requests with VCR and MiniTest

I just released moviesapi. In the post I introduced it, I mentioned wanting to be able to add reliable tests. Ben Keeping responsed suggesting that I have a look at VCR. So I did.

It’s a Ruby library that records the web requests that your application depends upon and saves it down to disk. On subsequent test runs, it reuses (“replays”) the previously saved data, vastly increasing the speed. For screenscraping tools like moviesapi or UrbanScraper, I can verify that my code is behaving correctly, even if the source has changed (this is another problem) and without constantly hitting the remote web service.

Tutorials covering both VCR and MiniTest were a little thin on the ground, so I thought I’d write one. As an introduction to MiniTest, I’d suggest Matt Sears’ Quick Reference post. I’d also suggest giving the VCR README at least a skim read.

The overall application I’m testing is a Sinatra one, but here, I’m more interested in testing the class that handles the screenscraping. Some of the MiniTest suggestions came from the Sinatra Recipes site.

Gemfile

Firstly, I added development and tests groups to my Gemfile, like so:

group :development, :test do
  gem 'minitest', '~> 5.0.6'
  gem 'webmock', '~> 1.12.0'
  gem 'vcr', '~> 2.5.0'
end

VCR is a high-level wrapper around a group of different web mocking libraries, webmock is just one of those supported. We’ll need this too.

MiniTest is actually already in the standard library from Ruby 1.9, but I’m keeping a reference for clarity (and a slightly newer version).

Spec Structure

I’m writing specs here, so everything is in a directory named “spec”:

spec/
    cassettes/
    support/vcr_setup.rb
    spec_helper.rb
    movie_spec.rb

cassettes holds the recorded requests. vcr_setup.rb contains VCR configuration (it’s loaded by spec_helper.rb). spec_helper.rb sets up the tests and provides any common configuration. Finally, movie_spec.rb is the spec I’ll be running. It’s from moviesapi.

vcr_setup.rb

require 'vcr'

VCR.configure do |c|
  c.cassette_library_dir = 'spec/cassettes'
  c.hook_into :webmock
end

This specifies where to find the cassettes — we assume everything is run from the root of the project. Then it specifices which mocking library to use, in this case, webmock.

spec_helper.rb

This contains common code to all of the specs (or tests). It’s typically used to load in the application and run any common load configuration (like setting ENV to test) and is then included in each spec (or test) to make it available on test run.

require 'minitest/autorun'
require 'minitest/pride'

# pull in the VCR setup
require File.expand_path './support/vcr_setup.rb', __dir__

# pull in the code to test
require File.expand_path '../movies.rb', __dir__

Rakefile

Finally, these additions to the Rakefile will allow your tests to be running according to typical Ruby conventions. It will also run the test suite as the default rake task:

require 'rake/testtask'

Rake::TestTask.new :spec do |t|
  t.test_files = Dir['spec/*_spec.rb']
end

task :default => :spec

For all of these helper files, they have been slimmed down a little. You may find the ones in the repo more helpful. (Also, these will work with Travis CI.)

Writing Specs that use VCR

A typical MiniTest spec looks a bit like this:

describe 'Something' do
  before do
    # something that should be done before a test starts
  end

  after do
    # something that should be done after a test ends
  end

  it 'does something' do
    # test things
  end
end

The MiniTest DSL provides several blocks that make up the spec. The describe block defines the behaviour you are specifying. The it block defines the test case. Then, inside here, “matchers” are used to confirm the output. MiniTest provides a reasonable collection of these in it’s docs, but you can also define your own1.

When using VCR with MiniTest, there are two approaches to work with. The first is to manually specify a cassette to encapsulate the test run. This is described in the VCR Getting Started Guide and looks a bit like this:

VCR.use_cassette('cassete name') do
  # the test
end

The second approach is to use the before and after blocks along with some runtime metadata that MiniTest provides. That looks a bit like this:

describe 'Movies' do
  before do
    VCR.insert_cassette name
  end

  after do
    VCR.eject_cassette
  end

  it 'fetches a list of cinemas' do
    # the test
  end
end

before and after are executed around each it block. So here, name is “fetches a list of cinemas”. If you were to have multiple it blocks, cassettes would be defined for each. The cassettes are then saved in spec/cassettes, in this case it is: test_0001_fetches_a_list_of_cinemas.yml. This is quite a nice approach for having a cassette dynamically defined for each spec.

For testing the result of the screenscraping, I have been checking the contents of the eventual data structure. Unlike with a typical web service, I can’t check the data that is contained within. Similarly, the cassettes are commited to the repository because I am checking for the behavioural correctness of my code — not that the web service/site has changed and broken it. For testing the behaviour of code that depends upon this, mocking will fit perfectly.

MiniTest combines with VCR quite nicely — especially once you work out the conventions to follow for structuring the test suite. If not for anything else, mocking out the web requests like this saves a signficant amount of time when testing web service interaction.