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.
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).
I’m writing specs here, so everything is in a directory named “
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 sets up the tests and provides
any common configuration. Finally,
movie_spec.rb is the spec I’ll be running.
It’s from moviesapi.
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
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
test) and is then included in each spec (or test) to make it available on test
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__
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
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 own[^gist].
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
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
after are executed around each
it block. So here,
“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
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.
[^gist]: This gist by Jared Ning contains a good set of examples of defining your own.