Go back to the main page

VCR Gem: Custom Matchers

This article is over 2 years old. Proceed with caution.

Regards ♨ – Minimul

 

 


Using custom matchers to help with the dreaded "VCR::Errors::UnhandledHTTPRequestError"

I can't say enough good things about the VCR Gem. I use it profusely when performing a QuickBooks & Ruby integration project. Recently, there was a situation where the quickbooks-ruby Gem had a slight change to the base API URI. The change caused one of my client's 30+ QuickBooks VCR-related specs to fail. I knew about custom matchers having already implemented one that disregards a trailing numeric id on a given URI. However, with this current problem I didn't want to have to add a { match_requests_on: [:method, :custom_matcher_name] to each of the existing failing cassettes. I wanted a global solution, preferably in my VCR.configure block that would handle this alteration in the quickbooks-ruby library. Come watch how the problem got solved and learn a bit more about custom matchers.

.. [a small library change] resulted in my client's 30+ VCR related specs to fail.

  1. To solve this issue I am going to leverage the VCR configure option default_cassette_options.
  2. VCR.configure options default_cassette_options. My VCR configure file is located at spec/support/vcr.rb
    This section is at the 3:35 mark in the screencast.
    Minimul says —

    Be aware that default_cassette_options will be run on each of your existing and future cassettes. I am going use this feature to filter out all cassettes that have request calls to intuit.com. Within that conditional block I am going to have all of my matching logic for QuickBooks specs. This will eliminate having to laboriously put 'match_requests_on' options to each individual cassette.

    See figure to aid my description.

    "Code spike" to demonstrate this approach of filtering all requests to QuickBooks.
  3. Next, I investigate the output of uri1 and uri2 by running one of the problem specs.
  4. The gem changed the URI from qb.sbfinance to quickbooks.api and that is why I am now getting the "VCR::Errors::UnhandledHTTPRequestError".
  5. At least both URIs have intuit.com in common so I can safely filter old and new cassettes by matching against it.
  6. Something like this.
    This section is at the 7:00 mark.
    Minimul says —

    Now that I am globally filtering out all cassettes with requests to intuit.com, I now need to fix the UnhandledHTTPRequestError. While I go about doing that I need to be concerned about making the matcher too lenient. If the custom matcher is not stringent enough it will make my QuickBooks specs prone to errors. I need to strive to make the matcher disregard only the difference between the quickbooks.api and the qb.sbfinance and not the rest of the URI.

    Be sure not to make your matcher too permissive and in the process diminishing the integrity of your VCR specs.
  7. So I decide to just remove the quickbooks.api and the qb.sbfinance. That leaves me with a more permissive matcher, which is strict enough to keep my VCR enabled specs honest.
  8. This is starting to look like it may solve my UnhandledHTTPRequestError.
  9. Next, I add the substituted URIs to the uri_matcher and the spec passes.
  10. Problem solved but there are some more considerations.
  11. Remember again, by using default_cassette_options that this matcher will run on every VCR cassette.
  12. Non intuit.com requests will be matched by a standard uri_matcher.matches?(request1, request2).
  13. I also rolled in a previous custom matcher, uri_ignore_trailing_id, within the new custom matcher.
  14. Move the existing custom matcher inside of the new default one.
  15. This is because setting individual cassettes with match_requests_on will result in the default_cassette_options being overridden. Therefore, VCR specs marked with an existing matcher such as uri_ignore_trailing_id will still not pass, hence the previous step of moving the uri_ignore_trailing_idmatcher inside of the for_intuit matcher.
  16. Inline matchers take precedence.

That concludes Custom matchers in VCR

This should give you a good starting point on how to create your own custom matchers. Be sure to view the screencast to get some more in depth commentary if this article is not clear enough. Finally, I want to give thanks to Myron Marston, the creator of VCR, for aiding me in this solution by answering my SO question.

Myron says —

Thanks for putting this together!

It's nice to have a screencast that shows how to use one of the more "power user" features of VCR.

For your particular use case, I'd actually recommend using a different VCR feature to solve it: define_cassette_placeholder:

Here's an example of it being used:

In your case, you could do something like:


VCR.configure do |c|
  c.define_cassette_placeholder("<quickbooks_host>") do
    QuickBooks.host # or whatever constant or config setting exposes the host
  end
end

When the cassettes are recorded, it'll put <quickbooks_host> in place of the real host name as a variable, and then during playback, it'll replace the variable, with current value returned by the block. This will allow it to continue to work whenever the host changes.

Comment on this article?