12

In my cucumber support directory I have the following in vcr.rb:

require 'vcr'

VCR.configure do |c|
  c.cassette_library_dir = 'fixtures/vcr_cassettes'
  c.hook_into :webmock
  c.ignore_localhost = true
  c.default_cassette_options = { record: :new_episodes }
end

I am geocoding city names which makes calls to the Google Maps API. I'm trying to record and stub these requests, but it keeps recording the same requests to the same yml file:

- request:
    method: get
    uri: http://maps.googleapis.com/maps/api/geocode/json?address=Miami,%20FL&language=en&sensor=false
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
      Accept:
      - ! '*/*'
      User-Agent:
      - Ruby
  # response...

- request:
    method: get
    uri: http://maps.googleapis.com/maps/api/geocode/json?address=Miami,%20FL&language=en&sensor=false
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
      Accept:
      - ! '*/*'
      User-Agent:
      - Ruby

It's the same URL and very same request, shouldn't VCR stub the request? How can I prevent my specs from hitting the API every time I try to search for the same city?

Myron Marston
  • 21,452
  • 5
  • 64
  • 63
Erik J
  • 828
  • 9
  • 22

4 Answers4

21

It's hard to say for sure what's going on with just what you've posted, but I can explain a bit more about how VCR works and make some guesses about possible reasons for this behavior.

VCR uses request matchers to try to find a previously recorded HTTP interaction to play back. During a single cassette session, when an HTTP interaction is played back, it is considered to be "used" and it will not be played back again (unless you use the allow_playback_repeats option).

So...here are a couple possibilities that come to mind:

  • Maybe VCR is unable to match your HTTP requests. What request matchers are you using? There are some easy ways to troubleshoot this (see below).
  • If you're not using :allow_playback_repeats (which is the default, and how I recommend you use VCR), then the behavior you're seeing could happen if multiple duplicate requests are being made in your test--e.g., maybe the cassette has only one matching request but you're test makes 2 of them--that would play back one and record one (since you're using :new_episodes).

To troubleshoot this, I recommend you use the debug_logger option to get VCR to print what it's doing and how it's trying to match each request. That should give you insight into what's going on. You can also override any of the built in request matchers and provide your own logic and/or set a breakpoint in the matcher:

VCR.configure do |c|
  c.register_request_matcher :uri do |request_1, request_2|
    debugger # so you can inspect the requests here
    request_1.uri == request_2.uri
  end
end

You may also have run into a VCR bug, although comparing the URIs (using String#==) is such a basic operation that I have a hard time imagining a bug there. Feel free to open a github issue (hopefully with the debug logger output and/or a code sample that triggers this) if you can't figure it out.

On a side note, I recommend you use the :once record mode (the default) rather than :new_episodes. :once will never record additional HTTP interactions to an existing cassette--it only allows the cassette to be recorded once. If a request fails to match, it'll raise an error alerting you to the fact it couldn't match. :new_episodes, on the other hand, will record any request that it can't find a match for, which is the behavior you're seeing.

Myron Marston
  • 21,452
  • 5
  • 64
  • 63
  • 2
    @Erik J - If there's *anyone* you should listen to about [VCR](https://github.com/myronmarston/vcr/) it's someone called Myron ;) – Gareth Jul 12 '12 at 01:43
  • Thanks, Myron, I hoped you'd come through ;) I just realized that my response headers include `Expires: - Fri, 13 Jul 2012 18:31:50 GMT` which is 24 hours after the request date. Does VCR notice this and try to make a request again? If so, is there a way to disable that? – Erik J Jul 12 '12 at 18:49
  • Nope, VCR doesn't use this at all. There is a [re_record_interval](https://www.relishapp.com/myronmarston/vcr/v/2-2-3/docs/cassettes/automatic-re-recording) option, but that doesn't look at any headers (it just uses the configured interval) and it wouldn't produce the duplicate interactions you're seeing; it would replace the old HTTP interaction instead of just adding a new one. – Myron Marston Jul 12 '12 at 23:22
  • BTW, I try to hang out in #vcr on irc.freenode.net when I'm online, so feel free to stop by if you want help in realtime. – Myron Marston Jul 12 '12 at 23:23
10

When I had a similar problem, I fixed it by making the match_requests_on setting more specific:

VCR.configure do |c|
    c.default_cassette_options = {
        match_requests_on: [:uri, :body, :method]
    }
end
Galen
  • 636
  • 6
  • 12
2

I experienced similar behaviour, so what I do is basically keeping to set to :none. If any new requests come up, I use :any, run the part of the test suite that did the requests and set it back to :none.

Seems like :new_episodes uses some strange heuristics to detemermine what new requests are and what requests already occurred. In our case it marked two different requests to the payment gateways as the same ones, leading to endless hours of debugging - because we got a RefundOk answer to a CaptureRequest and things like that. Better don't use :new_episodes...

iblue
  • 29,609
  • 19
  • 89
  • 128
  • Rather than the `:all`, `:none` toggling you mention, I recommend using the `:once` record mode. It acts like `:none` when a cassette already has already been recorded, or `:all` when it's a new cassette. – Myron Marston Jul 12 '12 at 05:17
  • The "strange heuristics" you refer to for `:new_episodes` are simply the configured request matchers. They work the same for all record modes. The effect of `:new_episodes` is that it will record requests that don't match any previously recorded ones; in contrast, `:once` and `:none` will raise an error when this occurs. – Myron Marston Jul 12 '12 at 05:19
0

Using Elasticsearch with VCR will always regenerate the cassettes if you not define the match parameters. I've to define the match to only :method because the :uri and :body could change each time that the test is running.

VCR.configure do |c|
  c.hook_into :webmock
  c.ignore_localhost = true
  c.configure_rspec_metadata!
  c.cassette_library_dir = 'spec/cassettes'
  c.default_cassette_options = { record: :new_episodes,
                                 match_requests_on: [:method] }
end
monteirobrena
  • 2,562
  • 1
  • 33
  • 45