Network integration tests with MiniTest::Spec and VCR

The problem

If your application relies on a remote network interface and you write an integration test against that code, you’ll notice that the speed of your tests will plumet due to the network delay you now have within your tests. Further whenever your coding in a place without internet connection your tests will fail leading you to ignoring them because you assume that it is a network issue. And thats a path you never want to go down with your tests.

VCR to the rescue

Jup the archaic technology for video recording will help you having a pleasent smile while writing integration tests for code that uses remote network interfaces. Further it will prevent you from having failing tests due to a flaky network connection.

VCR is a Gem by Myron Marston, which just recently hit the 2.0 version, and can be used with either the FakeWeb or Webmock gem.

Lets take a look at this integration test I’ve written in MiniTest spec.


it "returns the name of an episode for a season" do
  series_info = SeriesNamer::SeriesInfo.new("Burn Notice/Season 1", "Burn Notice", "Season 1")
  #series_name = "Burn Notice"
  season = 1
  episode = 3

  series_info = SeriesNamer::GetEpisodeNames.new(series_info)
  series_info.episode_name(season, episode).must_equal "S01E03 Fight or Flight"
end

So this test calls a class that gets the episode name of a series episode. It uses the TvDB to get the information.

Now how do we speed this test up? Well first lets go to our Gemfile and add the following gems:


group :test do
  gem 'vcr', '~>2.0.0'
  gem 'webmock', '~>1.8.3'
end

Then bundle install and we’re ready to go adding VCR (cassettes) to our project

Foreach testfile you want to use VCR in, you’ll have to add the following lines. You may want to put this into your spec_helper.rb [1] file to save you some work and help keeping your tests DRY.


require 'vcr'

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

Now we’re nearly there to get our speed boost with VCR. Only thing left is adding the VCR call VCR.use_cassette to our test:


it "returns the name of an episode for a season" do
  series_info = SeriesNamer::SeriesInfo.new("Burn Notice/Season 1", "Burn Notice", "Season 1")
  #series_name = "Burn Notice"
  season = 1
  episode = 3

  VCR.use_cassette('burn_notice_episode_1_3') do
    series_info = SeriesNamer::GetEpisodeNames.new(series_info)
    series_info.episode_name(season, episode).must_equal "S01E03 Fight or Flight"
  end
end

When executing this test the first time you will not see any changes but the next time and from there on you will no longer see your tests dragging along, actually you don’t even have to be connected to the net anymore to run your tests. VCR stored all the communication locally on your machine. How, when and where? Well lets have a look.

VCR records all the traffic between the do…end block and stores this information in a yaml file. The string passed into the use_cassette method is used by VCR as the name for the cassette i.e. the yaml file.

We defined where to store the cassettes in the spec_helper.rb file. With the line c.cassette_library_dir = 'spec/cassettes'. If we open that directory PATH_TO_PROJECT/spec/cassettes we see that there is a file burn_notice_episode_1_3.yml which contains all the communication that took place between your app and the network API.

So if you want to refresh your recording, just delete the directory and the next time you run your tests all the communication will be freshly recorded again.

Happy API integration testing!

Note: As long as the request doesn’t change you can reuse a cassette for multiple tests. As soon as the request/answer differs make sure to use a fresh cassette i.e. pass in a different string for the cassette name.

References

Railscasts #291
Rubyinside

[1] the spec_helper.rb file is a helper file you can add manually to your spec directory, if you haven’t done so already. All common groundwork code you use in different places within your tests you can put here or if the need arises split it up into finer grained helper classes. In case your using MiniTest/UnitTest without specs, you would name this file test_helper.rb and store it in the test directory.