I have a Rails 4 app that uses a custom authentication gem that authenticates users against a third-party API. The app requires authentication for most actions on the site (visitors can do very little).
I am trying to use VCR to record the api request made during authentication for all of the integration tests, but all examples that I can find on SO and the Relish documentation only cover how to do this with Rspec in a 'describe do' spec, as referenced here:
https://www.relishapp.com/vcr/vcr/v/1-6-0/docs/test-frameworks/usage-with-rspec
Since no customers are involved on this project, I am writing integration tests with Rspec and Capybara instead of Cucumber, so my tests are using the 'feature/scenario' format like so:
feature 'posts' do
scenario 'a user can log in' do
# use vcr for api request
sign_in_user # refers to a method that handles the api call to log in a user, which is what I would like VCR to record.
expect(page).to have_content("User signed in successfully")
end
end
Using the command described in the documentation:
use_vcr_cassette
inside of the 'scenario' block, returns an error:
Failure/Error: use_vcr_cassette
undefined local variable or method `use_vcr_cassette' for #<RSpec::ExampleGroups::Posts:0x007fb858369c38>
I followed the documentation to setup VCR in my spec/rails_helper.rb (which is included by the spec/spec_helper.rb)... which basically looks like this:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'support/vcr_cassettes'
c.hook_into :webmock
end
Obviously added gem 'vcr' to my Gemfile development/test group and it is a thing in console and binding.pry from inside of a test.
Has anyone used VCR inside of a Rspec feature? or have any suggestions on what I might do as a workaround?
Thanks in advance
Solution: Taryn East got me to the solution, but it is slightly different than the link posted for anyone trying to do this moving forward.
here is the most basic config in spec/rails_helper.rb or spec/spec_helper.rb:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'spec/cassettes'
c.hook_into :webmock
c.configure_rspec_metadata!
end
using c.configure_rspec_metadata! is required for Rspec to handle the :vcr tag.
And in an Rspec Feature spec:
feature 'users' do
scenario 'logged in users should be able to do stuff', :vcr do
# authenticate user or make other http request here
end
end
Oddly enough, in my tests - VCR is recording the response and if passes the first time, but fails the second time. I traced this to the response being stored differently than it is received.
On a normal request (using excon) like so:
resp = Excon.post(url, :body => data, :headers => { "Content-Type" => "application/x-www-form-urlencoded", "Authorization" => authorization_header })
The response has a header that is accessible in this format:
resp.headers["oauth_token"]
which returns an oauth token.
In the VCR response, it is being stored differently and only accessible as:
resp.headers["Oauth-Token"]
Which is weird, but workable. This may be a bug with VCR or some issue with Excon... too busy to figure that one out right now, but just a heads up in case anyone else uses this setup and gets a passing test with the live http request and a failing test when using the VCR cassette. A quick workaround is to either change the VCR cassette data to match what your code expects, or modify your code to accept either available value.
Related
I would like to have an ability of testing a remote 3d party API for endpoint responses, which is why I would like to write a bunch of local rspec tests and launch them periodically, to see if these endpoints are working as expected with no breaking changes. Since my application is highly dependent on this constantly changing API, I have little choice left but to automate my test.
At the moment I took a regular rspec API test code:
require "rails_helper"
RSpec.describe "Remote request", type: :request do
describe ".send request" do
it ".posts valid data" do
post "http://123.45.67.89/api/endpoint",
params: {
"job_request_id": 123456,
"data": "12345",
"app_secret": "12345",
"options": {
...
}
}
expect(JSON.parse response.body).to include("message" => "success")
expect(response).to have_http_status(200)
end
end
end
The problem with this code is that Rspec is hitting the /api/endpoint url, instead of the full http://123.45.67.89/api/endpoint url. How can I change this behaviour?
RSpec's request specs are meant for testing your own application - by sending real HTTP requests. They are not intended for performing remote HTTP requests (even if it is possible monkey with the configuration to request other hosts than localhost).
Among other things a request spec will hit the API for every example - this will give you problems with rate limiting and throttling.
While you could try using a HTTP library like Net::HTTP, Httparty or Typhoeus in a request spec - what you really should do is rethink your methodology. Its a good idea to isolate the interaction between your application and external collaborators.
One way of doing this is by creating client classes that consume the remote API:
class ExampleAPIClient
include HTTParty
base_url 'example.com'
format :json
def get_some_data
self.class.get('/foo')
end
end
You can then test the remote endpoint by testing the client like a Plain Old Ruby Object:
require 'spec_helper'
RSpec.describe ExampleAPIClient do
let(:client) { described_class.new }
describe "#get_some_data" do
let(:response) { client.get_some_data }
it "should be successful" do
expect(response.success?).to be_truthy
end
end
end
This will have the additional benefit of limiting exposure to change in your application as only a single component (the client) should fail if the remote api changes.
Other components which consume the client can stub out the remote interaction easily by stubbing the client.
In app we use 3-th part service, what sometimes get broken. We regular testing app by Capybara, Poltergeist. But for tests be more specific in error log I need catch in tests data response from get/post api calls to that 3-th par service. I know about Poltergeist method page.driver.network_traffic but there are no data here, useful for me only response.url and response.status , but also I want somehow get data. Thanks in advance.
Capybara is not suited or designed for API testing, see this blog post http://www.elabs.se/blog/34-capybara-and-testing-apis. There is no access to the get and post requests or responses without hacking the underlying code. Instead, try RackTest. RackTest was created to specifically test APIs: https://github.com/brynary/rack-test.
Edit: with rack-test the homepage documentation is not clear but you can use the Rack::Test::Methods mixin to get the response, see http://www.rubydoc.info/github/brynary/rack-test/Rack/Test/Methods.
For example:
require 'rack/test'
class MyTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
def my_test
get '/'
assert last_response.ok?
assert_equal '<expected_response>', last_response.body
end
end
I am using VCR with cucumber. I want to ask VCR to ignore all requests to "api.stripe.com" and do not deal with them. Let the requests go through to the real Stripe API server. But only those. I want the rest of the requests to be handled by VCR. So, I have the following VCR setup:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'vcr_cassettes'
c.hook_into :webmock
c.ignore_localhost = true
c.default_cassette_options = { :record => :new_episodes }
c.configure_rspec_metadata!
c.ignore_hosts 'api.stripe.com'
end
When I run my tests all the tests that are accessing other external services fail. For example:
#<VCR::Errors::UnhandledHTTPRequestError:
================================================================================
An HTTP request has been made that VCR does not know how to handle:
POST https://www.googleapis.com/urlshortener/v1/url?key=<my_key>
There is currently no cassette in use. There are a few ways
you can configure VCR to handle this request:
* 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].
* If you want VCR to record this request and play it back during
future test
runs, you should wrap your test (or this portion of your test) in
a `VCR.use_cassette` block [2].
* If you only want VCR to handle requests made while a cassette is
in use,
configure `allow_http_connections_when_no_cassette = true`. VCR
will
ignore this request since it is made when there is no cassette
[3].
* If you want VCR to ignore this request (and others like it), you
can
set an `ignore_request` callback [4].
So, really, I do not know what is going on here. Any help?
A bit late but perhaps you are not making the requests inside a VCR.use_cassette block or without the vcr metadata.
Please share a test example if this is not a solution.
I have a Rails 4.2 application....I was adding content compression via this thoughtbot blog post, but I get an error such as:
undefined method `get' for #<RSpec::ExampleGroups::Compression:0x00000009aa4cc8>
Perusing over the capybara docs, it seems like you shouldn't be using get. Any idea how to test the below then in Rails 4?
# spec/integration/compression_spec.rb
require 'spec_helper'
feature 'Compression' do
scenario "a visitor has a browser that supports compression" do
['deflate','gzip', 'deflate,gzip','gzip,deflate'].each do|compression_method|
get root_path, {}, {'HTTP_ACCEPT_ENCODING' => compression_method }
response.headers['Content-Encoding'].should be
end
end
scenario "a visitor's browser does not support compression" do
get root_path
response.headers['Content-Encoding'].should_not be
end
end
In a capybara test you would use visit not get (as described here), but that answer won't actually help you because the test you've written above is not an integration test, it's a controller test.
Move it to spec/controllers and use the controller-specific helpers describe/context/it etc. to construct your tests for your controller. You can set the headers and do the sorts of checks that you're doing in the code you're showing.
I'm working on integration my rails application with Recurly.js.
Before I was making requests to recurly from my server side application, therefore I was able to stub all my integration with excellent VCR gem (https://github.com/myronmarston/vcr) but Recurly.js makes request directly to the service from javascript code using JSONP.
The question is: how to mock these jsonp calls in the integration test?
Currently I'm using rspec + capybara + phantomjs driver (https://github.com/jonleighton/poltergeist)
The only approach I came up with is on-the-fly javascript patching. As far as the Poltergeist gem has a method to execute javascript right in the test browser, you could apply the following patch to turn Recurly.js into the test mode:
# The original 'save' function performs JSONP request to Recurly.
# A token is borrowed during the real API interaction.
page.driver.execute_script("""
Recurly.Subscription.save = function (options) {
Recurly.postResult('/subscription', { token: 'afc58c4895354255a422cc0405a045b0' }, options);
}
""")
Just make a capybara-macros, give a fancy name like 'stub_recurly_js' to it and invoke every time before submitting the Recurly.js forms.
Here is also a link to the original post if you want to dig a little deeper: http://pieoneers.tumblr.com/post/32406386853/test-recurlyjs-in-ruby-using-rspec-capybara-phantomjs
Use puffing-billy. It injects a proxy server between your test browser and the outside world, and allows you to fake responses for specific URLs.
Example:
describe 'my recurly jsonp spec' do
before do
# call proxy.stub to setup a fake response
proxy.stub 'https://api.recurly.com/v2/foo', :jsonp => { :bar => 'baz' }
end
it 'does something with recurly' do
....
end
end