Spying on Your Tests with VCR

July 5, 2013 Matthew Parker

VCR is a great tool for recording http requests during a test suite so that you can play them back later when the external server is not running or available. However, I’d like to show you how to abuse VCR into giving you the ability to spy on the network interactions during your test suite.

Here’s why: I wanted my integration test suite for AwesomeResource to kill two birds with one stone:

  1. Prove that it integrates correctly with a rails server that serializes models to JSON the default Rails way
  2. Create executable documentation for anyone that wants to look at the required JSON format

In other words, I needed to write a cucumber scenario like the following:


Scenario: Endpoint responds with 201
    When I call `create` on an Article model:
    """
      Article.create title: "foo"
    """

    Then the Article model should successfully POST the following JSON to "http://localhost:3001/articles":
    """
      {
        "article": {
          "title": "foo"
        }
      }
    """

    When I call the `all` method on the Article model

    Then the rails app should respond to a GET request to "http://localhost:3001/articles" with the following JSON:
    """
      {
        "articles": [
          {
            "title": "foo",
            "id": 1
          }
        ]
      }
    """

    And the `all` method should return the equivalent of:
    """
      [
        Article.new(
          id: 1,
          title: "foo"
        )
      ]
    """

If I was only interested in proving that AwesomeResource could integrate with the JSON format represented in this scenario, I could have written steps that faked out the Rails server.


When /^I call `create` on an Article model:$/ do |code|
  #no-op
end

Then /^the Article model should successfully POST the following JSON to "([^"]*)":$/ do |endpoint, json|
  Article.create_endpoint.should == endpoint
  Article.new(“title” => “foo”).to_json.should ==
end

When /^I call the `all` method on the Article model$/ do
  #no-op
end

Then /^the rails app should respond to a GET request to "([^"]*)" with the following JSON:$/ do |endpoint, json|
  Article.all_endpoint.should == endpoint
  @all_json_response = json
end

Then /^the `all` method should return the equivalent of:$/ do |code|
  Article.load_all_from_json(@all_json_response).should == eval(code)
end

But I wanted to take this a step further – I need to know when Rails changes the way it serializes and deserializes models to and from JSON. For years, the Rails ActiveResource library has sent broken JSON to Rails servers for nested associations when “ActiveResource.include_root_in_json” is on. Though the fix for this has now been released with Rails 4, I can’t help but wonder why didn’t they have an integration test suite that immediately told them when it broke? Why did they have to wait for community members to submit github issues? Because that’s ActiveResource. It’s not awesome. It’s just active.

The real AwesomeResource step definitions for the afore-mentioned scenario look like this:


When /^I call `create` on an Article model:$/ do |code|
  eval code
end

Then /^the Article model should successfully POST the following JSON to "([^"]*)":$/ do |endpoint, json|
  posts.should include_interaction(
    endpoint: endpoint,
    request_body: json,
    status: "201"
  )
end

When /^I call the `all` method on the Article model$/ do
  Article.all
end

Then /^the rails app should respond to a GET request to "([^"]*)" with the following JSON:$/ do |endpoint, json|
  gets.should include_interaction(
    endpoint: endpoint,
    response_body: json
  )
end

Then /^the `all` method should return the equivalent of:$/ do |code|
  Article.all.should == eval(code)
end

Before the test suite starts, it fires up a Rails server that can respond to CRUD requests for an “article” resource. Notice the `include_interaction` custom matcher? It’s actually iterating over all network requests that have been captured during the execution of the test thus far and finding one that matches the supplied criteria.

In order to capture all of the network interactions, I needed to spy on the network. After googling for a few minutes in vain for a gem that fit the bill, it occured to me that I could simply use “VCR” in “{record: :all}” mode – forcing it to rerecord (and thus not persist anything) during every request. With this in place, I can then create an “after_http_request” hook to snag and save each request/response cycle in memory for the test to access later:


Before do
  scenario_interactions = interactions

  VCR.configuration.clear_hooks

  VCR.configure do |c|
    c.after_http_request(->(_) { true }) do |request, response|
      scenario_interactions[request.method] ||= []
      scenario_interactions[request.method] << {request: request, response: response}
    end
  end
end

module Helpers
  def interactions
    @interactions ||= {}
  end

  def gets
    interactions[:get] ||= []
  end

  def posts
    interactions[:post] ||= []
  end

  def puts
    interactions[:put] ||= []
  end

  def deletes
    interactions[:delete] ||= []
  end
end

World Helpers

It gave me a sick sort of satisfaction to abuse VCR in this way. I have no idea if you’ll feel the same way. You can see the rest of the VCR configuration, helpers, and custom matchers up on github: https://github.com/moonmaster9000/awesome_resource

About the Author

Matthew Parker

Matt Parker is Head of Engineering for Pivotal Labs

Previous
SASS from a developer eyes
SASS from a developer eyes

I enjoy writing CSS more than the average developer, and I like when I have tools that can help me write be...

Next
Why Passbook Integration is a Must
Why Passbook Integration is a Must

This month, Apple’s Passbook application will mark its first birthday since being announced at Apple Worldw...