简体   繁体   中英

How to record/playback in different cassettes split by host?

In the project I am working on, we use VCR to store cassettes for both local and external services. The local ones are micro services that are constantly modified while the external ones are hardly modified.

Due this reason and plus that the external services takes a long time to be re-record, makes sense for us re-record just the local cassettes most of the time.

In order to solve that we tried to separate the cassettes in different folders (cassettes/localhost and cassettes/external/sample.com).

Then we came up with:

VCR.configure do |config|
  config.around_http_request do |request|
    host = URI(request.uri).host
    vcr_name = VCR.current_cassette.name
    folder = host
    folder = "external/#{folder}" if host != 'localhost'
    VCR.use_cassette("#{folder}/#{vcr_name}", &request)
  end
  [...]
end

But the problem is that we have some tests that need to make repeated requests (exactly the same request) where the server returns different results. So, using the code above makes the cassettes be reset for each http call. The first request is recorded, and the second is a playback of the first, even if the response was expected to be different.

Then we tried a different approach using tags and nested cassettes:

RSpec.configure do |config|
  config.around(:each) do |spec|
    name = spec.metadata[:full_description]
    VCR.use_cassette "external/#{name}", tag: :external do
      VCR.use_cassette "local/#{name}", tag: :internal do
        spec.call
      end
    end
  end
  [...]
end

VCR.configure do |config|
  config.before_record(:external) do |i|
    i.ignore! if URI(i.request.uri).host == 'localhost'
  end

  config.before_record(:internal) do |i|
    i.ignore! if URI(i.request.uri).host != 'localhost'
  end
  [...]
end

But also this doesn't work. The outcome was that all localhost requests were recorded on the internal cassette. The rest of the requests were ignored by VCR.

So do you have any suggestion to solve this?

I think you want to look into using the :match_requests_on setting. Read the docs here: https://www.relishapp.com/myronmarston/vcr/v/2-3-0/docs/request-matching

This should allow you to record multiple requests to the same URL but have them replayed in sequence.

In addition to that, I think your method of splitting up the cassettes into different directories sounds good. One thing I've done in the past to force re-recording of specific cassettes, is to just delete the cassettes themselves before rerunning the specs. In your case that should be easy since you've separated them nicely.

Instead of or in addition to that, you could possibly use the :all record setting when you know it's a local request, and patch that into your configure block. Something like:

VCR.configure do |config|
  config.around_http_request do |request|
    host = URI(request.uri).host
    vcr_name = VCR.current_cassette.name
    folder = host
    if host != 'localhost'
      folder = "external/#{folder}"
      record_mode = :once
    else
      record_mode = :all
    end
    VCR.use_cassette("#{folder}/#{vcr_name}", :record => record_mode, &request)
  end
  [...]
end

Note, I haven't tested this, so please double check me on that. Of course, you'd also want to not use the :all record setting when you just want to play things back. Maybe you can develop a switch somehow when you invoke the tests.

It's not a complete answer, but I hope this helps.

Then we tried a different approach using tags and nested cassettes...But also this doesn't work.

Yeah, I didn't design cassette nesting with this kind of use case in mind. HTTP interactions are always recorded to the innermost cassette, but can be played back from any level of nesting (it tries the innermost cassette first, then searches up the parent chain). The main use case I had in mind for nesting cassettes was for cucumber: you may want to use a single cassette for an entire scenario, but then you may want to use a particular cassette for an individual step definition (ie for any scenario that uses that step). The inner cassette "takes over" while it is in use, but the outer cassette is still there to be available for when the inner cassette is ejected.

This is an interesting use case, though...if you think VCR should work that way, please consider opening a github issue for it and we can discuss it more.

As for your original question: I think @operand's answer will work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM