Question

Situation: testing a rails application using Rspec, FactoryGirl and VCR.

Every time a User is created, an associated Stripe customer is created through Stripe's API. While testing, it doesn't really makes sense to add a VCR.use_cassette or describe "...", vcr: {cassette_name: 'stripe-customer'} do ... to every spec where User creation is involved. My actual solution is the following:

RSpec.configure do |config|
  config.around do |example|
    VCR.use_cassette('stripe-customer') do |cassette|
      example.run
    end
  end
end

But this isn't sustainable because the same cassette will be used for every http request, which of course is very bad.

Question: How can I use specific fixtures (cassettes) based on individual request, without specifying the cassette for every spec?

I have something like this in mind, pseudo-code:

stub_request(:post, "api.stripe.com/customers").with(File.read("cassettes/stripe-customer"))

Relevant pieces of code (as a gist):

# user_observer.rb

class UserObserver < ActiveRecord::Observer

  def after_create(user)
    user.create_profile!

    begin
      customer =  Stripe::Customer.create(
        email: user.email,
        plan: 'default'
        )

      user.stripe_customer_id = customer.id
      user.save!
    rescue Stripe::InvalidRequestError => e
      raise e
    end

  end
end


# vcr.rb

require 'vcr'

VCR.configure do |config|
  config.default_cassette_options = { record: :once, re_record_interval: 1.day }
  config.cassette_library_dir = 'spec/fixtures/cassettes'
  config.hook_into :webmock
  config.configure_rspec_metadata!
end


# user_spec.rb

describe :InstanceMethods do
  let(:user) { FactoryGirl.create(:user) }

  describe "#flexible_name" do
    it "returns the name when name is specified" do
      user.profile.first_name = "Foo"
      user.profile.last_name = "Bar"

      user.flexible_name.should eq("Foo Bar")
    end
  end
end

Edit

I ended doing something like this:

VCR.configure do |vcr|
  vcr.around_http_request do |request|

    if request.uri =~ /api.stripe.com/
      uri = URI(request.uri)
      name = "#{[uri.host, uri.path, request.method].join('/')}"
      VCR.use_cassette(name, &request)

    elsif request.uri =~ /twitter.com/
      VCR.use_cassette('twitter', &request)
    else
    end

  end
end
Was it helpful?

Solution

VCR 2.x includes a feature specifically to support use cases like these:

https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/before-http-request-hook! https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/after-http-request-hook! https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/around-http-request-hook!

VCR.configure do |vcr|
  vcr.around_http_request(lambda { |req| req.uri =~ /api.stripe.com/ }) do |request|
    VCR.use_cassette(request.uri, &request)
  end
end

OTHER TIPS

IMO, libraries like this should provided you with a mock class, but w/e.


You can do your pseudocode example already with Webmock, which is the default internet mocking library that VCR uses.

body = YAML.load(File.read 'cassettes/stripe-customer.yml')['http_interactions'][0]['response']['body']['string']
stub_request(:post, "api.stripe.com/customers").to_return(:body => body)

You could put that in a before block that only runs on a certain tag, then tag the requests that make API calls.


In their tests, they override the methods that delegate to RestClient (link). You could do this as well, take a look at their test suite to see how they use it, in particular their use of test_response. I think this is a terribly hacky way of doing things, and would feel really uncomfortable with it (note that I'm in the minority with this discomfort) but it should work for now (it has the potential to break without you knowing until runtime). If I were to do this, I'd want to build out real objects for the two mocks (the one mocking rest-client, and the other mocking the rest-client response).

The whole point (mostly anyway) of VCR to just to replay the response of a previous request. If you are in there picking and choosing what response goes back to what request, you are quote/unquote doing-it-wrong.

Like Joshua already said, you should use Webmock for something like this. That's what VCR is uing behind the scenes anyway.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top