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:
- Prove that it integrates correctly with a rails server that serializes models to JSON the default Rails way
- 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