vcr
helps you stub and replay your HTTP requests. The main use case is for unit tests for R packages. An R port of the Ruby gem vcr
Check out the HTTP testing book and the vcr vignettes.
library(vcr)
library(crul)
cli <- crul::HttpClient$new(url = "https://eu.httpbin.org")
system.time(
use_cassette(name = "helloworld", {
cli$get("get")
})
)
#> user system elapsed
#> 0.123 0.023 0.557
The request gets recorded, and all subsequent requests of the same form used the cached HTTP response, and so are much faster
system.time(
use_cassette(name = "helloworld", {
cli$get("get")
})
)
#> user system elapsed
#> 0.079 0.005 0.086
Importantly, your unit test deals with the same inputs and the same outputs - but behind the scenes you use a cached HTTP response - thus, your tests run faster.
The cached response looks something like (condensed for brevity):
http_interactions:
- request:
method: get
uri: https://eu.httpbin.org/get
body:
encoding: ''
string: ''
headers:
User-Agent: libcurl/7.54.0 r-curl/3.2 crul/0.5.2
response:
status:
status_code: '200'
message: OK
explanation: Request fulfilled, document follows
headers:
status: HTTP/1.1 200 OK
connection: keep-alive
body:
encoding: UTF-8
string: "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"application/json,
text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\",
\n \"Connection\": \"close\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\":
\"libcurl/7.54.0 r-curl/3.2 crul/0.5.2\"\n }, \n \"origin\": \"111.222.333.444\",
\n \"url\": \"https://eu.httpbin.org/get\"\n}\n"
recorded_at: 2018-04-03 22:55:02 GMT
recorded_with: vcr/0.1.0, webmockr/0.2.4, crul/0.5.2
All components of both the request and response are preserved, so that the HTTP client (in this case crul
) can reconstruct its own response just as it would if it wasn’t using vcr
.
vcr_setup()
the default directory created to hold cassettes is called fixtures/
as a signal as to what the folder contains.The Steps
vcr::use_cassette
or vcr::insert_cassette
vcr::insert_cassette
, make sure to run vcr::eject_cassette
when you’re done to stop recordingvcr
there’s no cached data to use, so we allow HTTP requests until you’re request is done.webmockr
so that future requests will match the stub. This stub is an R6 class with details of the interaction (request + response), but is not on disk.When you run that request again using vcr::use_cassette
or vcr::insert_cassette
:
webmockr
to match the request to cached requests, and since we stubbed the request the first time we used the cached response.Of course if you do a different request, even slightly (but depending on which matching format you decided to use), then the request will have no matching stub and no cached response, and then a real HTTP request is done - we then cache it, then subsequent requests will pull from that cached response.
webmockr
has adapters for each R client (again, right now only crul) - so that we actually intercept HTTP requests when webmockr
is loaded and the user turns it on. So, webmockr
doesn’t actually require an internet or localhost connection at all, but can do its thing just fine by matching on whatever the user requests to match on. In fact, webmockr
doesn’t allow real HTTP requests by default, but can be toggled off of course.
The main use case we are going for in vcr
is to deal with real HTTP requests and responses, so we allow real HTTP requests when we need to, and turn it off when we don’t.
This gives us a very flexible and powerful framework where we can support webmockr
and vcr
integration for any number of R clients for HTTP requests and support many different formats serialized to disk.
You’re looking for webmockr. webmockr
only matches requests based on criteria you choose, but does not cache HTTP interactions to disk as vcr
does.
vcr
to Suggests
in your DESCRIPTION file (optionally add webmockr
, but it’s not explicitly needed as vcr
will pull it in)tests/testthat/
directory called helper-yourpackage.R
(or skip if as similar file already exists). In that file use the following lines to setup your path for storing cassettes (change path to whatever you want):vcr
, wrap them in a vcr::use_cassette()
call like:library(testthat)
vcr::use_cassette("rl_citation", {
test_that("my test", {
aa <- rl_citation()
expect_is(aa, "character")
expect_match(aa, "IUCN")
expect_match(aa, "www.iucnredlist.org")
})
})
OR put the vcr::use_cassette()
block on the inside, but put testthat
expectations outside of the vcr::use_cassette()
block:
library(testthat)
test_that("my test", {
vcr::use_cassette("rl_citation", {
aa <- rl_citation()
})
expect_is(aa, "character")
expect_match(aa, "IUCN")
expect_match(aa, "www.iucnredlist.org")
})
Don’t wrap the use_cassette()
block inside your test_that()
block with testthat
expectations inside the use_cassette()
block, as you’ll only get the line number that the use_cassette()
block starts on on failures.
devtools::check()
vs. devtools::test()
. It’s not clear why this would make a difference. Do let us know if you run into this problem.You can use vcr
in an R project as well.
vcr
in your projectuse_cassette
to run code that does HTTP requests.CRAN version:
Development version:
We set the following defaults:
dir
= “.”record
= “once”match_requests_on
= c("method", "uri")
allow_unused_http_interactions
= TRUE
serialize_with
= "yaml"
persist_with
= "FileSystem"
ignore_hosts
= NULL
ignore_localhost
= FALSE
uri_parser
= crul::url_parse
preserve_exact_body_bytes
= FALSE
turned_off
= FALSE
ignore_cassettes
= FALSE
re_record_interval
= NULL
clean_outdated_http_interactions
= NULL
cassettes
= list()
linked_context
= NULL
vcr_logging
= "vcr.log"
vcr_logging_opts
= list()
You can get the defaults programmatically with
You can change all the above defaults with vcr_configure()
:
Calling vcr_configuration()
gives you some of the more important defaults in a nice tidy print out
vcr
looks for similarity in your HTTP requests to cached requests. You can set what is examined about the request with one or more of the following options:
body
headers
host
method
path
query
uri
By default, we use method
(HTTP method, e.g., GET
) and uri
(test for exact match against URI, e.g., http://foo.com
).
You can set your own options by tweaking the match_requests_on
parameter:
use_cassette(name = "one", {
cli$post("post", body = list(a = 5))
},
match_requests_on = c('method', 'headers', 'body')
)
The canonical vcr
(in Ruby) lists ports in other languages at https://github.com/vcr/vcr
There’s a number of features in this package that are not yet supported, but for which their parameters are found in the package. For example, decode_compressed_response
is a parameter in use_cassette()
but it is ignored right now.
We’ve tried to make sure the parameters that are ignored are marked as such. Keep an eye out for package updates for changes in these parameters, and/or let us know you want it and we can move it up in the priority list.
vcr
in R doing citation(package = 'vcr')