Stubbing API Calls in RSpec/Capybara with Puffing Billy and VCR

About a year ago, when I first discovered and started using VCR to stub the external API calls being made during the run of an RSpec test suite, I thought all my testing woes were over.

At last (I thought), an easy way to mock and even record the interactions with external APIs while running my tests! One-year-ago-me was so naive.

Capybara + VCR != <3

What I didn't understand one year ago, was that VCR and Webmock (or whatever mocking service you choose), will intercept and mock any web requests made by Ruby.

So what? (You might be wondering). I'm testing a Rails app and the services I've built to interact with external APIs are written in Ruby.

Well, this is true. Except when it isn't. With our use of Ajax to provide a seamless UX, our Rails applications frequently make web requests from the client-side.

Providing test coverage for features that make client-side web requests leaves us with two options––don't stub web requests made by the client, and end up with a slow test suite, or, manually mock each and every call that running out test suite will enact.

We definitely don't want to settle for the first option––not only will our tests be slow, but we will be making real API calls, potentially posting real data and using our real rate limit, for the purposes of testing. The second option also has some significant drawbacks. Manually mocking each and every API call can be painstaking and time consuming. For example, the particular API calls I was looking to make in a recent project involved pulling files from every pull request opened on a given GitHub repo. That is just too many calls to mock (in my humble and lazy opinion).

Puffing Billy and Client-Side Mocking

If only there was some VCR-like option for stubbing and recording web requests made from the client-side of our Rails app...

Guess what? There is! (You're so surprised, I know). Thanks to Puffing Billy, there is a gem that does exactly that. Puffing Billy spawns an HTTP proxy server that it uses to intercept requests from your browser. It provides a fairly simple DSL for specifying what requests to mock and how.

But What About Features that Make Server-Side and Client-Side Requests?

What happens when we have a feature that uses Ajax to send one request, and server-side Ruby to make another call to an external API? While VCR can only record requests made by Ruby, Puffing Billy will only intercept and cache requests made from the client-side.

This is exactly the situation I found myself in yesterday, when attempting to test a feature with the following flow:

  • User clicks a button, "get pull requests"
  • This sends an Ajax POST request to the back-end of my Rails app (specifically to the #create action of the PullRequestsController.
  • This action calls on a service I defined that uses Octokit to talk to the GitHub API. Here, we send a request to GitHub for all the pull requests of a given repository.

We can see that this feature involves a client-side request and a server side call to the GitHub API.

Why is this so problematic from an integration testing perspective? Well, we can't simply use VCR to stub the server-side request. The button click that sends the Ajax request that eventually makes that server-side API call won't be intercepted by VCR.

If we try to use VCR on integration tests that use Capybara and the :js => true designation (for our Ajax request), the following test will raise an error:

describe "GET /get_pull_requests", js: true do
 it "retreives student PRs for a repo when 'get prs' button is clicked" do
   test_seed
   cohort = Cohort.first
   lab = Lab.create(name: "Simple Math", repo: 
     "https//github.com/learn-co-students/simple-math-web-
      0416", cohort: cohort)
        
  VCR.use_cassette('retreive_prs_feature_test') do
    visit cohort_lab_path(cohort, lab)
    click_link "get-prs-btn"
    sleep(10)
  end

   expect(lab.pull_requests.count).to eq(1)
  end
end
  An error occurred in an after hook
    VCR::Errors::UnhandledHTTPRequestError: 

========================================================
An HTTP request has been made that VCR does not know how to handle:
  GET https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls

VCR is currently using the following cassette:
  - /Users/SophieDeBenedetto/Dev/flatiron-lab-trackr/spec/vcr/fixtures/retreive_pull_requests_feature_test.yml
    - :record => :once
    - :match_requests_on => [:method, :uri]

Under the current configuration VCR can not find a suitable HTTP interaction
to replay and is prevented from recording new requests. There are a few ways
you can deal with this:

  * If you're surprised VCR is raising this error
    and want insight about how VCR attempted to handle the request,
    you can use the debug_logger configuration option to log more details [1].
  * You can use the :new_episodes record mode to allow VCR to
    record this new request to the existing cassette [2].
  * If you want VCR to ignore this request (and others like it), you can
    set an `ignore_request` callback [3].
  * The current record mode (:once) does not allow new requests to be recorded
    to a previously recorded cassette. You can delete the cassette file and re-run
    your tests to allow the cassette to be recorded with this request [4].
  * The cassette contains 48 HTTP interactions that have not been
    played back. If your request is non-deterministic, you may need to
    change your :match_requests_on cassette option to be more lenient
    or use a custom request matcher to allow it to match [5].

This error is being raised because the feature we are testing makes an Ajax POST request before firing off the server-side request to the GitHub API.

Similarly, we can't just use Puffing Billy here either. While it will mock the Ajax POST request that gets us to the back-end, it won't intercept the API call made by our Octokit-wrapping service in the controller. This would still leave us with a slow test suite that relies on an internet connection.

Luckily for us, thanks to Swizec Teller's contributions to Puffing Billy, we can use Puffing Billy together with VCR to mock and cache the necessary client-side requests and record server-side requests within the same integration test.

Let's do it!

Stubbing Web Requests in Capybara Integration Tests with VCR + Puffing Billy

First, install the necessary gems:

group :test do 
  gem 'rspec-rails', '~> 3.0'
  gem 'vcr'
  gem 'webmock'
  gem 'puffing-billy'
  gem 'capybara'
  gem 'capybara-webkit'
end

Note: I was originally using the Selenium web driver with Capybara, but I kept experiencing time out errors. Switching to Capybara WebKit seems to have resolved the issue for now.

Bundle install and then you're ready to configure VCR and Puffing Billy.

VCR Configuration

We'll configure VCR in spec/support/vcr_setup.rb:

VCR.configure do |c|  
  c.cassette_library_dir = 'spec/vcr/fixtures'
  c.ignore_localhost = true
  c.filter_sensitive_data('< GITHUB_USERNAME >') { ENV['GITHUB_USERNAME'] }  
  c.filter_sensitive_data('< GITHUB_PASSWORD >') { ENV['GITHUB_PASSWORD'] }  
  c.hook_into :webmock
end

Make sure you set the ignore_local_host option to true. Remember that Capybara starts a localhost server and pings it when we use Capybara with a JavaScript driver. If we don't set this option to true, then VCR will prevent Capybara from running on its JS driver.

Capybara Configuration

We'll configure Capybara in spec/support/cabypara_setup.rb:


Capybara.javascript_driver = :webkit_billy
Capybara::Webkit.configure do |config|
  config.allow_url("google-code-prettify.googlecode.com")
  config.allow_url("https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js")
end

Let's take a closer look at this configuration. First, we're registering the webkit_billy JavaScript driver, which we have access to thanks to our Puffing Billy gem and our Capybara WebKit gem.

Then, we're whitelisting the Google Prettify URLs that my application sends requests to from the client-side (I'm using the Prettify CDN, in case that wasn't clear). This way, Capybara will be allowed to make those requests, and Puffing Billy can stub and cache those requests.

This isn't strictly necessary, as we don't need Google Prettify to run our test.But I wanted to expose this configuration for those who may want to apply it in a different scenario.

Puffing Billy Configuration

We'll configure Puffing Billy in spec/support/bill_setup.rb:

Billy.configure do |c|
  c.cache = true
  c.persist_cache = true
  c.cache_path = 'spec/req_cache/'
end
 
# need to call this because of a race condition between persist_cache
# being set and the proxy being loaded for the first time
Billy.proxy.reset_cache

Here, we set the cache configuration to true, so that our stubbed requests have their responses cached, (or recorded, just like VCR!), to the directory specified in the cache_path option.

spec_helper Configuration

Lastly, remember to require all of our setup files in the spec_helper:

require 'capybara/rspec'
require 'rails_helper'
require 'webmock/rspec'  
require 'rspec/retry'

require_relative "./support/deep_struct.rb"
require_relative "./support/vcr_setup.rb"
require_relative "./support/capybara_setup.rb"
require_relative "./support/billy_setup.rb"

RSpec.configure do |config|
  config.include Capybara::DSL
 

  config.use_transactional_fixtures = false
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, type: :feature) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.append_after(:each) do
    DatabaseCleaner.clean
  end


  config.verbose_retry = true
  config.display_try_failure_messages = true

  # run retry only on features
  config.around :each, :js do |ex|
    ex.run_with_retry retry: 3
  end

  config.around(:each, type: :feature) do |example|
    WebMock.allow_net_connect!
    example.run
    WebMock.disable_net_connect!(allow_localhost: true)
  end

  config.expect_with :rspec do |expectations|
 expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

A couple of important things to note here:

  • I'm using rspec/retry on my feature tests, since I was dealing with random time out errors earlier on.
  • We're specifying that WebMock should allow for web connections and VCR recordings on our feature tests that specify :js => true.
config.around(:each, type: :feature) do |example|
  WebMock.allow_net_connect!
  example.run
  WebMock.disable_net_connect!(allow_localhost: true)
end

Now that everything is set up, we're ready to run our feature test!

Executing Our Integration Test

Let's keep the logs for our Puffing Billy proxy running so we can watch what happens.

In one terminal window:

tail -f log/* | grep puffing-billy

In another:

rspec spec/features/get_student_pull_requests_spec.rb

Our Puffing Billy logs should show that the client-side requests are being mocked:

puffing-billy: Proxy listening on http://localhost:50094
puffing-billy: CACHE KEY for 'https://google-code-prettify.googlecode.com:443/svn/loader/run_prettify.js' is 'get_google-code-prettify.googlecode.com_879dc0c2dcb9771788f4191fa0854af0b1cd7e87'
puffing-billy: PROXY POST succeeded for 'http://127.0.0.1:65395/pull_requests?lab=simple-math'

That last line is the successful stub of our Ajax POST request.

Meanwhile, VCR was able to step in and record the requests to the GitHub API made by our back-end.

# spec/vcr/fixtures/retrieve_pull_requests_feature.yml

---
http_interactions:
- request:
    method: get
    uri: https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 4.3.0
      Content-Type:
      - application/json
      Authorization:
      - Basic c29waGllLmRlYmVuZWRldHRvQGdtYWlsLmNvbTpGdms2ZGdpdg==
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 200
      message: OK
    headers:
      Server:
      - GitHub.com
      Date:
      - Sat, 07 May 2016 04:50:38 GMT
      Content-Type:
      - application/json; charset=utf-8
      Transfer-Encoding:
      - chunked
      Status:
      - 200 OK
      X-Ratelimit-Limit:
      - '5000'
      X-Ratelimit-Remaining:
      - '4417'
      X-Ratelimit-Reset:
      - '1462597655'
      Cache-Control:
      - private, max-age=60, s-maxage=60
      Vary:
      - Accept, Authorization, Cookie, X-GitHub-OTP
      - Accept-Encoding
      Etag:
      - W/"5a63e687e7597272ce7370075cdb0c33"
      X-Github-Media-Type:
      - github.v3; format=json
      Access-Control-Expose-Headers:
      - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
        X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
      Access-Control-Allow-Origin:
      - "*"
      Content-Security-Policy:
      - default-src 'none'
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Content-Type-Options:
      - nosniff
      X-Frame-Options:
      - deny
      X-Xss-Protection:
      - 1; mode=block
      X-Served-By:
      - a241e1a8264a6ace03db946c85b92db3
      X-Github-Request-Id:
      - 18BE539E:2CBC:52C4999:572D741D
    body:
      encoding: ASCII-8BIT
      string: '[{"url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1","id":64084414,"html_url":"https://github.com/learn-co-students/simple-math-web-0416/pull/1","diff_url":"https://github.com/learn-co-students/simple-math-web-0416/pull/1.diff","patch_url":"https://github.com/learn-co-students/simple-math-web-0416/pull/1.patch","issue_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/1","number":1,"state":"open","locked":false,"title":"Done.","user":{"login":"cjtafoya","id":16465308,"avatar_url":"https://avatars.githubusercontent.com/u/16465308?v=3","gravatar_id":"","url":"https://api.github.com/users/cjtafoya","html_url":"https://github.com/cjtafoya","followers_url":"https://api.github.com/users/cjtafoya/followers","following_url":"https://api.github.com/users/cjtafoya/following{/other_user}","gists_url":"https://api.github.com/users/cjtafoya/gists{/gist_id}","starred_url":"https://api.github.com/users/cjtafoya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjtafoya/subscriptions","organizations_url":"https://api.github.com/users/cjtafoya/orgs","repos_url":"https://api.github.com/users/cjtafoya/repos","events_url":"https://api.github.com/users/cjtafoya/events{/privacy}","received_events_url":"https://api.github.com/users/cjtafoya/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-03-24T19:14:57Z","updated_at":"2016-03-24T19:14:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"88b1b2f2538b9c89cfd162658f13d88a978d1b8c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1/commits","review_comments_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1/comments","review_comment_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/comments{/number}","comments_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/1/comments","statuses_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/statuses/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","head":{"label":"cjtafoya:master","ref":"master","sha":"9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","user":{"login":"cjtafoya","id":16465308,"avatar_url":"https://avatars.githubusercontent.com/u/16465308?v=3","gravatar_id":"","url":"https://api.github.com/users/cjtafoya","html_url":"https://github.com/cjtafoya","followers_url":"https://api.github.com/users/cjtafoya/followers","following_url":"https://api.github.com/users/cjtafoya/following{/other_user}","gists_url":"https://api.github.com/users/cjtafoya/gists{/gist_id}","starred_url":"https://api.github.com/users/cjtafoya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjtafoya/subscriptions","organizations_url":"https://api.github.com/users/cjtafoya/orgs","repos_url":"https://api.github.com/users/cjtafoya/repos","events_url":"https://api.github.com/users/cjtafoya/events{/privacy}","received_events_url":"https://api.github.com/users/cjtafoya/received_events","type":"User","site_admin":false},"repo":{"id":54667700,"name":"simple-math-web-0416","full_name":"cjtafoya/simple-math-web-0416","owner":{"login":"cjtafoya","id":16465308,"avatar_url":"https://avatars.githubusercontent.com/u/16465308?v=3","gravatar_id":"","url":"https://api.github.com/users/cjtafoya","html_url":"https://github.com/cjtafoya","followers_url":"https://api.github.com/users/cjtafoya/followers","following_url":"https://api.github.com/users/cjtafoya/following{/other_user}","gists_url":"https://api.github.com/users/cjtafoya/gists{/gist_id}","starred_url":"https://api.github.com/users/cjtafoya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjtafoya/subscriptions","organizations_url":"https://api.github.com/users/cjtafoya/orgs","repos_url":"https://api.github.com/users/cjtafoya/repos","events_url":"https://api.github.com/users/cjtafoya/events{/privacy}","received_events_url":"https://api.github.com/users/cjtafoya/received_events","type":"User","site_admin":false},"private":true,"html_url":"https://github.com/cjtafoya/simple-math-web-0416","description":null,"fork":true,"url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416","forks_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/forks","keys_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/teams","hooks_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/hooks","issue_events_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/issues/events{/number}","events_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/events","assignees_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/assignees{/user}","branches_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/branches{/branch}","tags_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/tags","blobs_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/git/refs{/sha}","trees_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/statuses/{sha}","languages_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/languages","stargazers_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/stargazers","contributors_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/contributors","subscribers_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/subscribers","subscription_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/subscription","commits_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/commits{/sha}","git_commits_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/git/commits{/sha}","comments_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/comments{/number}","issue_comment_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/issues/comments{/number}","contents_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/contents/{+path}","compare_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/merges","archive_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/downloads","issues_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/issues{/number}","pulls_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/pulls{/number}","milestones_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/milestones{/number}","notifications_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/labels{/name}","releases_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/releases{/id}","deployments_url":"https://api.github.com/repos/cjtafoya/simple-math-web-0416/deployments","created_at":"2016-03-24T19:12:17Z","updated_at":"2016-03-24T19:12:18Z","pushed_at":"2016-03-24T19:14:44Z","git_url":"git://github.com/cjtafoya/simple-math-web-0416.git","ssh_url":"git@github.com:cjtafoya/simple-math-web-0416.git","clone_url":"https://github.com/cjtafoya/simple-math-web-0416.git","svn_url":"https://github.com/cjtafoya/simple-math-web-0416","homepage":null,"size":14,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"learn-co-students:master","ref":"master","sha":"f2ec36d64ad3cb955f47286350ee19a9fa30cfcb","user":{"login":"learn-co-students","id":8825476,"avatar_url":"https://avatars.githubusercontent.com/u/8825476?v=3","gravatar_id":"","url":"https://api.github.com/users/learn-co-students","html_url":"https://github.com/learn-co-students","followers_url":"https://api.github.com/users/learn-co-students/followers","following_url":"https://api.github.com/users/learn-co-students/following{/other_user}","gists_url":"https://api.github.com/users/learn-co-students/gists{/gist_id}","starred_url":"https://api.github.com/users/learn-co-students/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/learn-co-students/subscriptions","organizations_url":"https://api.github.com/users/learn-co-students/orgs","repos_url":"https://api.github.com/users/learn-co-students/repos","events_url":"https://api.github.com/users/learn-co-students/events{/privacy}","received_events_url":"https://api.github.com/users/learn-co-students/received_events","type":"Organization","site_admin":false},"repo":{"id":46754456,"name":"simple-math-web-0416","full_name":"learn-co-students/simple-math-web-0416","owner":{"login":"learn-co-students","id":8825476,"avatar_url":"https://avatars.githubusercontent.com/u/8825476?v=3","gravatar_id":"","url":"https://api.github.com/users/learn-co-students","html_url":"https://github.com/learn-co-students","followers_url":"https://api.github.com/users/learn-co-students/followers","following_url":"https://api.github.com/users/learn-co-students/following{/other_user}","gists_url":"https://api.github.com/users/learn-co-students/gists{/gist_id}","starred_url":"https://api.github.com/users/learn-co-students/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/learn-co-students/subscriptions","organizations_url":"https://api.github.com/users/learn-co-students/orgs","repos_url":"https://api.github.com/users/learn-co-students/repos","events_url":"https://api.github.com/users/learn-co-students/events{/privacy}","received_events_url":"https://api.github.com/users/learn-co-students/received_events","type":"Organization","site_admin":false},"private":true,"html_url":"https://github.com/learn-co-students/simple-math-web-0416","description":null,"fork":false,"url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416","forks_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/forks","keys_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/keys{/key_id}","collaborators_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/teams","hooks_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/hooks","issue_events_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/events{/number}","events_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/events","assignees_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/assignees{/user}","branches_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/branches{/branch}","tags_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/tags","blobs_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/refs{/sha}","trees_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/trees{/sha}","statuses_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/statuses/{sha}","languages_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/languages","stargazers_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/stargazers","contributors_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contributors","subscribers_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/subscribers","subscription_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/subscription","commits_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/commits{/sha}","git_commits_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/commits{/sha}","comments_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/comments{/number}","issue_comment_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/comments{/number}","contents_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/{+path}","compare_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/compare/{base}...{head}","merges_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/merges","archive_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/downloads","issues_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues{/number}","pulls_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls{/number}","milestones_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/milestones{/number}","notifications_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/labels{/name}","releases_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/releases{/id}","deployments_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/deployments","created_at":"2015-11-23T23:29:15Z","updated_at":"2016-04-04T14:00:48Z","pushed_at":"2016-04-04T14:00:47Z","git_url":"git://github.com/learn-co-students/simple-math-web-0416.git","ssh_url":"git@github.com:learn-co-students/simple-math-web-0416.git","clone_url":"https://github.com/learn-co-students/simple-math-web-0416.git","svn_url":"https://github.com/learn-co-students/simple-math-web-0416","homepage":null,"size":15,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1"},"html":{"href":"https://github.com/learn-co-students/simple-math-web-0416/pull/1"},"issue":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/1"},"comments":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/statuses/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad"}}}]'
    http_version: 
  recorded_at: Sat, 07 May 2016 04:50:36 GMT
- request:
    method: get
    uri: https://api.github.com/repos/learn-co-students/simple-math-web-0416/pulls/1/files
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 4.3.0
      Content-Type:
      - application/json
      Authorization:
      - Basic c29waGllLmRlYmVuZWRldHRvQGdtYWlsLmNvbTpGdms2ZGdpdg==
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 200
      message: OK
    headers:
      Server:
      - GitHub.com
      Date:
      - Sat, 07 May 2016 04:50:38 GMT
      Content-Type:
      - application/json; charset=utf-8
      Transfer-Encoding:
      - chunked
      Status:
      - 200 OK
      X-Ratelimit-Limit:
      - '5000'
      X-Ratelimit-Remaining:
      - '4416'
      X-Ratelimit-Reset:
      - '1462597655'
      Cache-Control:
      - private, max-age=60, s-maxage=60
      Vary:
      - Accept, Authorization, Cookie, X-GitHub-OTP
      - Accept-Encoding
      Etag:
      - W/"0764e91816c98f58e41272ee74817dd4"
      Last-Modified:
      - Tue, 26 Apr 2016 13:23:32 GMT
      X-Github-Media-Type:
      - github.v3; format=json
      Access-Control-Expose-Headers:
      - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
        X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
      Access-Control-Allow-Origin:
      - "*"
      Content-Security-Policy:
      - default-src 'none'
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Content-Type-Options:
      - nosniff
      X-Frame-Options:
      - deny
      X-Xss-Protection:
      - 1; mode=block
      X-Served-By:
      - cee4c0729c8e9147e7abcb45b9d69689
      X-Github-Request-Id:
      - 18BE539E:2CBA:3754BDC:572D741E
    body:
      encoding: ASCII-8BIT
      string: '[{"sha":"6b67ea38b453d9986de71c24e017825086f807a3","filename":".results.json","status":"added","additions":1,"deletions":0,"changes":1,"blob_url":"https://github.com/learn-co-students/simple-math-web-0416/blob/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/.results.json","raw_url":"https://github.com/learn-co-students/simple-math-web-0416/raw/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/.results.json","contents_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/.results.json?ref=9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","patch":"@@
        -0,0 +1 @@\n+{\"version\":\"3.4.1\",\"examples\":[{\"description\":\"adds
        two numbers together\",\"full_description\":\"#addition adds two numbers together\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":4,\"run_time\":0.000527,\"pending_message\":null},{\"description\":\"subtracts
        two numbers from each other\",\"full_description\":\"#subtraction subtracts
        two numbers from each other\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":10,\"run_time\":8.4e-05,\"pending_message\":null},{\"description\":\"divides
        one number by another\",\"full_description\":\"#division divides one number
        by another\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":16,\"run_time\":0.000125,\"pending_message\":null},{\"description\":\"multiplies
        two numbers together\",\"full_description\":\"#multiplication multiplies two
        numbers together\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":22,\"run_time\":7.2e-05,\"pending_message\":null},{\"description\":\"returns
        the remainder of two numbers\",\"full_description\":\"#modulo returns the
        remainder of two numbers\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":28,\"run_time\":0.000128,\"pending_message\":null},{\"description\":\"returns
        the square root of a number\",\"full_description\":\"#square_root returns
        the square root of a number\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":34,\"run_time\":9.3e-05,\"pending_message\":null},{\"description\":\"uses
        parenthesis to set the order of operations properly\",\"full_description\":\"order_of_operations
        uses parenthesis to set the order of operations properly\",\"status\":\"passed\",\"file_path\":\"./spec/math_spec.rb\",\"line_number\":40,\"run_time\":7.0e-05,\"pending_message\":null}],\"summary\":{\"duration\":0.00247,\"example_count\":7,\"failure_count\":0,\"pending_count\":0},\"summary_line\":\"7
        examples, 0 failures\"}\n\\ No newline at end of file"},{"sha":"d0571d212f28dc41a74bf3c452ab726a84997705","filename":"lib/math.rb","status":"modified","additions":7,"deletions":1,"changes":8,"blob_url":"https://github.com/learn-co-students/simple-math-web-0416/blob/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/lib/math.rb","raw_url":"https://github.com/learn-co-students/simple-math-web-0416/raw/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/lib/math.rb","contents_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/lib/math.rb?ref=9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","patch":"@@
        -1,21 +1,27 @@\n def addition(num1, num2)\n+  num1+num2\n end\n \n def subtraction(num1,
        num2)\n+  num1-num2\n end\n \n def division(num1, num2)\n+  num1/num2\n end\n
        \n def multiplication(num1, num2)\n+  num1*num2\n end\n \n def modulo(num1,
        num2)\n+  num1%num2\n end\n \n def square_root(num)\n+  Math.sqrt(num)\n end\n
        \n def order_of_operation(num1, num2, num3, num4)\n-  #Hint:  __ + (( __ *
        __ ) / __ )\n+  num1 + (( num2 * num3 ) / num4 )\n end"}]'
    http_version: 
  recorded_at: Sat, 07 May 2016 04:50:36 GMT
- request:
    method: get
    uri: https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/lib/math.rb?ref=9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad&since
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 4.3.0
      Content-Type:
      - application/json
      Authorization:
      - Basic c29waGllLmRlYmVuZWRldHRvQGdtYWlsLmNvbTpGdms2ZGdpdg==
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 200
      message: OK
    headers:
      Server:
      - GitHub.com
      Date:
      - Sat, 07 May 2016 04:50:38 GMT
      Content-Type:
      - application/json; charset=utf-8
      Transfer-Encoding:
      - chunked
      Status:
      - 200 OK
      X-Ratelimit-Limit:
      - '5000'
      X-Ratelimit-Remaining:
      - '4415'
      X-Ratelimit-Reset:
      - '1462597655'
      Cache-Control:
      - private, max-age=60, s-maxage=60
      Vary:
      - Accept, Authorization, Cookie, X-GitHub-OTP
      - Accept-Encoding
      Etag:
      - W/"3630bda5e47ae857a4f91fdb32fb5be0"
      Last-Modified:
      - Thu, 24 Mar 2016 19:14:43 GMT
      X-Github-Media-Type:
      - github.v3; format=json
      Access-Control-Expose-Headers:
      - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
        X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
      Access-Control-Allow-Origin:
      - "*"
      Content-Security-Policy:
      - default-src 'none'
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Content-Type-Options:
      - nosniff
      X-Frame-Options:
      - deny
      X-Xss-Protection:
      - 1; mode=block
      X-Served-By:
      - a30e6f9aa7cf5731b87dfb3b9992202d
      X-Github-Request-Id:
      - 18BE539E:2CBC:52C49FB:572D741E
    body:
      encoding: ASCII-8BIT
      string: '{"name":"math.rb","path":"lib/math.rb","sha":"d0571d212f28dc41a74bf3c452ab726a84997705","size":346,"url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/lib/math.rb?ref=9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","html_url":"https://github.com/learn-co-students/simple-math-web-0416/blob/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/lib/math.rb","git_url":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/blobs/d0571d212f28dc41a74bf3c452ab726a84997705","download_url":"https://raw.githubusercontent.com/learn-co-students/simple-math-web-0416/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/lib/math.rb?token=AIlSrGHmJ3Cu6F8eHmc-dX-lf74Ds_eKks5XLXRawA%3D%3D","type":"file","content":"ZGVmIGFkZGl0aW9uKG51bTEsIG51bTIpCiAgbnVtMStudW0yCmVuZAoKZGVm\nIHN1YnRyYWN0aW9uKG51bTEsIG51bTIpCiAgbnVtMS1udW0yCmVuZAoKZGVm\nIGRpdmlzaW9uKG51bTEsIG51bTIpCiAgbnVtMS9udW0yCmVuZAoKZGVmIG11\nbHRpcGxpY2F0aW9uKG51bTEsIG51bTIpCiAgbnVtMSpudW0yCmVuZAoKZGVm\nIG1vZHVsbyhudW0xLCBudW0yKQogIG51bTElbnVtMgplbmQKCmRlZiBzcXVh\ncmVfcm9vdChudW0pCiAgTWF0aC5zcXJ0KG51bSkKZW5kCgpkZWYgb3JkZXJf\nb2Zfb3BlcmF0aW9uKG51bTEsIG51bTIsIG51bTMsIG51bTQpCiAgbnVtMSAr\nICgoIG51bTIgKiBudW0zICkgLyBudW00ICkKZW5kCg==\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/contents/lib/math.rb?ref=9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad","git":"https://api.github.com/repos/learn-co-students/simple-math-web-0416/git/blobs/d0571d212f28dc41a74bf3c452ab726a84997705","html":"https://github.com/learn-co-students/simple-math-web-0416/blob/9769cd1e7fc2d929d313ed6f90dc8ce8fd54d9ad/lib/math.rb"}}'
  http_version: 
  recorded_at: Sat, 07 May 2016 04:50:37 GMT
recorded_with: VCR 3.0.1

Subsequent runs of our test suite will use Puffing Billy with our saved cache of client-side responses and read from this recorded cassette.

Conclusion

I should also share, before you go, that I did experience frequent and seemingly random time out errors. Switching to WebKit from Selenium, as the JS driver for Capybara, helped a little, as did using the RSpec Retry gem.

All in all, Puffing Billy and VCR work together really well, although it took me a little while to get the configuration just right. I just want to reiterate that, in order to provide mocking coverage for a feature that makes both client-side and server-side requests, we need to configure WebMock to allow for connections during our feature tests, while still using VCR.

And that's it!

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus