Predefined strategies
VCR has next predefined matchers:
- :method – HTTP method (i.e. GET, POST, PUT or DELETE) of the request
- :uri – full URI of the request
- :host – host of the URI(without port)
- :path – path of the URI
- :query – query string values of the URI
- :body – body of the request(PUT or POST methods)
- :headers – request headers
Variable query part
The most common case for mutable URI – is some timestamp or another random data in query-part of request. VCR provides special helper for such cases. Use:# for ignoring one parameter
VCR.request_matchers.uri_without_param(:timestamp)
# for ignoring several parameters
VCR.request_matchers.uri_without_params(:timestamp, :session)
Using this helper VCR matches on the whole URI, but excludes :timestamp(and :session) from matching in query-part of URI. Here is an example of usage in RSpec:describe "#fetch_info", vcr: {match_requests_on: [:method,
VCR.request_matchers.uri_without_param(:timestamp)]} do
# ...
end
Variable path part
Sometimes, especially for REST-API, where you dynamically generate user_id in test and do request to remote server, request path can vary on ID. To skip matching on variable ID, you can write custom URI-matcher. In fact, this way can be used(and should be) when you can’t assemble required matching strategy from standard VCR-matchers.Custom matcher can be any Proc-object which accepts two VCR::Request objects. Here is an implementation for trailing ID URI:# URI example:
# http://example.com/users/123
# matches to
# http://example.com/users/124
uri_ignoring_trailing_id = lambda do |request_1, request_2|
uri1, uri2 = request_1.uri, request_2.uri
regexp_trail_id = %r(/\d+/?\z)
if uri1.match(regexp_trail_id)
r1_without_id = uri1.gsub(regexp_trail_id, "")
r2_without_id = uri2.gsub(regexp_trail_id, "")
uri1.match(regexp_trail_id) && uri2.match(regexp_trail_id) && r1_without_id == r2_without_id
else
uri1 == uri2
end
end
describe "#upgrade", vcr: {match_requests_on: [:method, uri_ignoring_trailing_id]} do
#...
end
Notice, we wrote matcher like variable, not like symbol.Request-object has next API:- #method – symbol(:put, :post, etc)
- #uri – string, full non-parsed URI
- #body – string, POST/PUT request body
- #headers – Hash, all request headers
- #parsed_uri – URI-object(with scheme, host, port, path, and query accessors)
port = lambda do |r1, r2|
r1.parsed_uri.port == r2.parsed_uri.port
end
describe "#remote_call", vcr: {match_requests_on: [:method, :host, port]} do
#...
end
Variable response
Custom matchers work great if response body isn’t parametrized by request information. If this is a case, you should leverage VCR callbacks and it can be a subject for the whole new article. But there is a good example of such usage at https://github.com/nbibler/vcr-284. It can hint you with required approach.Anyway, if stuff becomes too complex, it’s much easier to directly use webmock, fakeweb or any other mocking framework which is used by your configuration of VCR.Move custom matchers into configuration
In case, you want to reuse custom matcher, it can be put in RSpec configuration:VCR.configure do |c|
c.register_request_matcher :uri_without_timestamp, &VCR.request_matchers.uri_without_param(:timestamp)
c.register_request_matcher :port do |r1, r2|
r1.parsed_uri.port == r2.parsed_uri.port
end
c.register_request_matcher :uri_ignoring_trailing_id do |request_1, request_2|
uri1, uri2 = request_1.uri, request_2.uri
regexp_trail_id = %r(/\d+/?\z)
if uri1.match(regexp_trail_id)
r1_without_id = uri1.gsub(regexp_trail_id, "")
r2_without_id = uri2.gsub(regexp_trail_id, "")
uri1.match(regexp_trail_id) && uri2.match(regexp_trail_id) && r1_without_id == r2_without_id
else
uri1 == uri2
end
end
end
Now you can use :uri_without_timestamp, :port, and :uri_ignoring_trailing_id in tests:describe "#fetch_info", vcr: {match_requests_on: [:method, :uri_without_timestamp]} do
#...
end
describe "#remote_call", vcr: {match_requests_on: [:method, :host, :port]} do
#...
end
describe "#upgrade", vcr: {match_requests_on: [:method, :uri_ignoring_trailing_id]} do
#...
end
Debug
Sometimes, things go wrong and you need a way to sneak into matching process. Drop this code into any test and it enables debug-mode:VCR.configure do |c|
c.debug_logger = STDOUT
end
Now you are fully armed and ready to use VCR at its fullest.