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 thePullRequestsController
. - 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!