Title: | Record 'HTTP' Calls to Disk |
---|---|
Description: | Record test suite 'HTTP' requests and replays them during future runs. A port of the Ruby gem of the same name (<https://github.com/vcr/vcr/>). Works by hooking into the 'webmockr' R package for matching 'HTTP' requests by various rules ('HTTP' method, 'URL', query parameters, headers, body, etc.), and then caching real 'HTTP' responses on disk in 'cassettes'. Subsequent 'HTTP' requests matching any previous requests in the same 'cassette' use a cached 'HTTP' response. |
Authors: | Scott Chamberlain [aut, cre] |
Maintainer: | Scott Chamberlain <[email protected]> |
License: | MIT + file LICENSE |
Version: | 1.6.0.91 |
Built: | 2024-08-26 05:04:31 UTC |
Source: | https://github.com/ropensci/vcr |
Coerce names, etc. to cassettes
Coerce to a cassette path
as.cassette(x, ...) as.cassettepath(x)
as.cassette(x, ...) as.cassettepath(x)
x |
Input, a cassette name (character), or something that can be coerced to a cassette |
... |
further arguments passed on to |
a cassette of class Cassette
## Not run: vcr_configure(dir = tempfile()) insert_cassette("foobar") cassettes(on_disk = FALSE) cassettes(on_disk = TRUE) as.cassette("foobar", on_disk = FALSE) eject_cassette() # eject the current cassette # cleanup unlink(file.path(tempfile(), "foobar.yml")) ## End(Not run)
## Not run: vcr_configure(dir = tempfile()) insert_cassette("foobar") cassettes(on_disk = FALSE) cassettes(on_disk = TRUE) as.cassette("foobar", on_disk = FALSE) eject_cassette() # eject the current cassette # cleanup unlink(file.path(tempfile(), "foobar.yml")) ## End(Not run)
List cassettes, get current cassette, etc.
cassettes(on_disk = TRUE, verb = FALSE) current_cassette() cassette_path()
cassettes(on_disk = TRUE, verb = FALSE) current_cassette() cassette_path()
on_disk |
(logical) Check for cassettes on disk + cassettes in session
( |
verb |
(logical) verbose messages |
cassettes()
: returns cassettes found in your R session, you can toggle
whether we pull from those on disk or not
current_cassette()
: returns an empty list when no cassettes are in use,
while it returns the current cassette (a Cassette
object) when one is
in use
cassette_path()
: just gives you the current directory path where
cassettes will be stored
vcr_configure(dir = tempdir()) # list all cassettes cassettes() cassettes(on_disk = FALSE) # list the currently active cassette insert_cassette("stuffthings") current_cassette() eject_cassette() cassettes() cassettes(on_disk = FALSE) # list the path to cassettes cassette_path() vcr_configure(dir = file.path(tempdir(), "foo")) cassette_path() vcr_configure_reset()
vcr_configure(dir = tempdir()) # list all cassettes cassettes() cassettes(on_disk = FALSE) # list the currently active cassette insert_cassette("stuffthings") current_cassette() eject_cassette() cassettes() cassettes(on_disk = FALSE) # list the path to cassettes cassette_path() vcr_configure(dir = file.path(tempdir(), "foo")) cassette_path() vcr_configure_reset()
Check cassette names
check_cassette_names( pattern = "test-", behavior = "stop", allowed_duplicates = NULL )
check_cassette_names( pattern = "test-", behavior = "stop", allowed_duplicates = NULL )
pattern |
(character) regex pattern for file paths to check.
this is done inside of |
behavior |
(character) "stop" (default) or "warning". if "warning",
we use |
allowed_duplicates |
(character) cassette names that can be duplicated |
Cassette names:
Should be meaningful so that it is obvious to you what test/function they relate to. Meaningful names are important so that you can quickly determine to what test file or test block a cassette belongs. Note that vcr cannot check that your cassette names are meaningful.
Should not be duplicated. Duplicated cassette names would lead to a test using the wrong cassette.
Should not have spaces. Spaces can lead to problems in using file paths.
Should not include a file extension. vcr handles file extensions for the user.
Should not have illegal characters that can lead to problems in using
file paths: /
, ?
, <
, >
, \
, :
, *
, |
, and \"
Should not have control characters, e.g., \n
Should not have just dots, e.g., .
or ..
Should not have Windows reserved words, e.g., com1
Should not have trailing dots
Should not be longer than 255 characters
vcr::check_cassette_names()
is meant to be run during your tests, from
a helper-*.R
file
inside the tests/testthat
directory. It only checks that cassette
names are not duplicated. Note that if you do need to have duplicated
cassette names you can do so by using the allowed_duplicates
parameter
in check_cassette_names()
. A helper function check_cassette_names()
runs inside insert_cassette()
that checks that
cassettes do not have: spaces, file extensions, unaccepted characters
(slashes).
The object is a list, and is the object that is passed on to webmockr and vcr instead of routing through crul as normal. Used in examples/tests.
A list
Eject a cassette
eject_cassette( cassette = NULL, options = list(), skip_no_unused_interactions_assertion = NULL )
eject_cassette( cassette = NULL, options = list(), skip_no_unused_interactions_assertion = NULL )
cassette |
(character) a single cassette names to eject; see |
options |
(list) a list of options to apply to the eject process |
skip_no_unused_interactions_assertion |
(logical) If |
The ejected cassette if there was one
use_cassette()
, insert_cassette()
vcr_configure(dir = tempdir()) insert_cassette("hello") (x <- current_cassette()) # by default does current cassette x <- eject_cassette() x # can also select by cassette name # eject_cassette(cassette = "hello")
vcr_configure(dir = tempdir()) insert_cassette("hello") (x <- current_cassette()) # by default does current cassette x <- eject_cassette() x # can also select by cassette name # eject_cassette(cassette = "hello")
Get the http interactions of the current cassette
http_interactions()
http_interactions()
object of class HTTPInteractionList
if there is a current
cassette in use, or NullList
if no cassette in use
## Not run: vcr_configure(dir = tempdir()) insert_cassette("foo_bar") webmockr::webmockr_allow_net_connect() library(crul) cli <- crul::HttpClient$new("https://hb.opencpu.org/get") one <- cli$get(query = list(a = 5)) z <- http_interactions() z z$interactions z$used_interactions # on eject, request written to the cassette eject_cassette("foo_bar") # insert cassette again insert_cassette("foo_bar") # now interactions will be present z <- http_interactions() z$interactions z$used_interactions invisible(cli$get(query = list(a = 5))) z$used_interactions # cleanup unlink(file.path(tempdir(), "foo_bar.yml")) ## End(Not run)
## Not run: vcr_configure(dir = tempdir()) insert_cassette("foo_bar") webmockr::webmockr_allow_net_connect() library(crul) cli <- crul::HttpClient$new("https://hb.opencpu.org/get") one <- cli$get(query = list(a = 5)) z <- http_interactions() z z$interactions z$used_interactions # on eject, request written to the cassette eject_cassette("foo_bar") # insert cassette again insert_cassette("foo_bar") # now interactions will be present z <- http_interactions() z$interactions z$used_interactions invisible(cli$get(query = list(a = 5))) z$used_interactions # cleanup unlink(file.path(tempdir(), "foo_bar.yml")) ## End(Not run)
object holds request and response objects
Methods
to_hash()
Create a hash from the HTTPInteraction object
from_hash(hash)
Create a HTTPInteraction object from a hash
request
A Request
class object
response
A VcrResponse
class object
recorded_at
(character) Time http interaction recorded at
new()
Create a new HTTPInteraction
object
HTTPInteraction$new(request, response, recorded_at)
request
A Request
class object
response
A VcrResponse
class object
recorded_at
(character) Time http interaction recorded at
A new HTTPInteraction
object
to_hash()
Create a hash from the HTTPInteraction object
HTTPInteraction$to_hash()
a named list
from_hash()
Create a HTTPInteraction object from a hash
HTTPInteraction$from_hash(hash)
hash
a named list
a new HttpInteraction
object
clone()
The objects of this class are cloneable with this method.
HTTPInteraction$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: # make the request library(vcr) url <- "https://hb.opencpu.org/post" body <- list(foo = "bar") cli <- crul::HttpClient$new(url = url) res <- cli$post(body = body) # build a Request object (request <- Request$new("POST", uri = url, body = body, headers = res$response_headers)) # build a VcrResponse object (response <- VcrResponse$new( res$status_http(), res$response_headers, res$parse("UTF-8"), res$response_headers$status)) # make HTTPInteraction object (x <- HTTPInteraction$new(request = request, response = response)) x$recorded_at x$to_hash() # make an HTTPInteraction from a hash with the object already made x$from_hash(x$to_hash()) # Make an HTTPInteraction from a hash alone my_hash <- x$to_hash() HTTPInteraction$new()$from_hash(my_hash) ## End(Not run)
## Not run: # make the request library(vcr) url <- "https://hb.opencpu.org/post" body <- list(foo = "bar") cli <- crul::HttpClient$new(url = url) res <- cli$post(body = body) # build a Request object (request <- Request$new("POST", uri = url, body = body, headers = res$response_headers)) # build a VcrResponse object (response <- VcrResponse$new( res$status_http(), res$response_headers, res$parse("UTF-8"), res$response_headers$status)) # make HTTPInteraction object (x <- HTTPInteraction$new(request = request, response = response)) x$recorded_at x$to_hash() # make an HTTPInteraction from a hash with the object already made x$from_hash(x$to_hash()) # Make an HTTPInteraction from a hash alone my_hash <- x$to_hash() HTTPInteraction$new()$from_hash(my_hash) ## End(Not run)
keeps track of all HTTPInteraction objects
Private Methods
has_unused_interactions()
Are there any unused interactions? returns boolean
matching_interaction_index_for()
asdfadf
matching_used_interaction_for(request)
asdfadfs
interaction_matches_request(request, interaction)
Check if a request matches an interaction (logical)
from_hash()
Get a hash back.
request_summary(z)
Get a request summary (character)
response_summary(z)
Get a response summary (character)
interactions
(list) list of interaction class objects
request_matchers
(character) vector of request matchers
allow_playback_repeats
whether to allow playback repeats
parent_list
A list for empty objects, see NullList
used_interactions
(list) Interactions that have been used
new()
Create a new HTTPInteractionList
object
HTTPInteractionList$new( interactions, request_matchers, allow_playback_repeats = FALSE, parent_list = NullList$new(), used_interactions = list() )
interactions
(list) list of interaction class objects
request_matchers
(character) vector of request matchers
allow_playback_repeats
whether to allow playback repeats or not
parent_list
A list for empty objects, see NullList
used_interactions
(list) Interactions that have been used. That is, interactions that are on disk in the current cassette, and a request has been made that matches that interaction
A new HTTPInteractionList
object
response_for()
Check if there's a matching interaction, returns a response object
HTTPInteractionList$response_for(request)
request
The request from an object of class HTTPInteraction
has_interaction_matching()
Check if has a matching interaction
HTTPInteractionList$has_interaction_matching(request)
request
The request from an object of class HTTPInteraction
logical
has_used_interaction_matching()
check if has used interactions matching a given request
HTTPInteractionList$has_used_interaction_matching(request)
request
The request from an object of class HTTPInteraction
logical
remaining_unused_interaction_count()
Number of unused interactions
HTTPInteractionList$remaining_unused_interaction_count()
integer
assert_no_unused_interactions()
Checks if there are no unused interactions left.
HTTPInteractionList$assert_no_unused_interactions()
various
clone()
The objects of this class are cloneable with this method.
HTTPInteractionList$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: vcr_configure( dir = tempdir(), record = "once" ) # make interactions ## make the request ### turn off mocking crul::mock(FALSE) url <- "https://hb.opencpu.org/post" cli <- crul::HttpClient$new(url = url) res <- cli$post(body = list(a = 5)) ## request (request <- Request$new("POST", url, list(a = 5), res$headers)) ## response (response <- VcrResponse$new( res$status_http(), res$response_headers, res$parse("UTF-8"), res$response_headers$status)) ## make an interaction (inter <- HTTPInteraction$new(request = request, response = response)) # make an interactionlist (x <- HTTPInteractionList$new( interactions = list(inter), request_matchers = vcr_configuration()$match_requests_on )) x$interactions x$request_matchers x$parent_list x$parent_list$response_for() x$parent_list$has_interaction_matching() x$parent_list$has_used_interaction_matching() x$parent_list$remaining_unused_interaction_count() x$used_interactions x$allow_playback_repeats x$interactions x$response_for(request) ## End(Not run)
## Not run: vcr_configure( dir = tempdir(), record = "once" ) # make interactions ## make the request ### turn off mocking crul::mock(FALSE) url <- "https://hb.opencpu.org/post" cli <- crul::HttpClient$new(url = url) res <- cli$post(body = list(a = 5)) ## request (request <- Request$new("POST", url, list(a = 5), res$headers)) ## response (response <- VcrResponse$new( res$status_http(), res$response_headers, res$parse("UTF-8"), res$response_headers$status)) ## make an interaction (inter <- HTTPInteraction$new(request = request, response = response)) # make an interactionlist (x <- HTTPInteractionList$new( interactions = list(inter), request_matchers = vcr_configuration()$match_requests_on )) x$interactions x$request_matchers x$parent_list x$parent_list$response_for() x$parent_list$has_interaction_matching() x$parent_list$has_used_interaction_matching() x$parent_list$remaining_unused_interaction_count() x$used_interactions x$allow_playback_repeats x$interactions x$response_for(request) ## End(Not run)
Insert a cassette to record HTTP requests
insert_cassette( name, record = NULL, match_requests_on = NULL, update_content_length_header = FALSE, allow_playback_repeats = FALSE, serialize_with = NULL, persist_with = NULL, preserve_exact_body_bytes = NULL, re_record_interval = NULL, clean_outdated_http_interactions = NULL )
insert_cassette( name, record = NULL, match_requests_on = NULL, update_content_length_header = FALSE, allow_playback_repeats = FALSE, serialize_with = NULL, persist_with = NULL, preserve_exact_body_bytes = NULL, re_record_interval = NULL, clean_outdated_http_interactions = NULL )
name |
The name of the cassette. vcr will check this to ensure it
is a valid file name. Not allowed: spaces, file extensions, control
characters (e.g., |
record |
The record mode (default: |
match_requests_on |
List of request matchers
to use to determine what recorded HTTP interaction to replay. Defaults to
|
update_content_length_header |
(logical) Whether or
not to overwrite the |
allow_playback_repeats |
(logical) Whether or not to
allow a single HTTP interaction to be played back multiple times.
Default: |
serialize_with |
(character) Which serializer to use. Valid values are "yaml" (default) and "json". Note that you can have multiple cassettes with the same name as long as they use different serializers; so if you only want one cassette for a given cassette name, make sure to not switch serializers, or clean up files you no longer need. |
persist_with |
(character) Which cassette persister to use. Default: "file_system". You can also register and use a custom persister. |
preserve_exact_body_bytes |
(logical) Whether or not
to base64 encode the bytes of the requests and responses for
this cassette when serializing it. See also |
re_record_interval |
(integer) How frequently (in seconds) the
cassette should be re-recorded. default: |
clean_outdated_http_interactions |
(logical) Should outdated
interactions be recorded back to file? default: |
Cassette names:
Should be meaningful so that it is obvious to you what test/function they relate to. Meaningful names are important so that you can quickly determine to what test file or test block a cassette belongs. Note that vcr cannot check that your cassette names are meaningful.
Should not be duplicated. Duplicated cassette names would lead to a test using the wrong cassette.
Should not have spaces. Spaces can lead to problems in using file paths.
Should not include a file extension. vcr handles file extensions for the user.
Should not have illegal characters that can lead to problems in using
file paths: /
, ?
, <
, >
, \
, :
, *
, |
, and \"
Should not have control characters, e.g., \n
Should not have just dots, e.g., .
or ..
Should not have Windows reserved words, e.g., com1
Should not have trailing dots
Should not be longer than 255 characters
vcr::check_cassette_names()
is meant to be run during your tests, from
a helper-*.R
file
inside the tests/testthat
directory. It only checks that cassette
names are not duplicated. Note that if you do need to have duplicated
cassette names you can do so by using the allowed_duplicates
parameter
in check_cassette_names()
. A helper function check_cassette_names()
runs inside insert_cassette()
that checks that
cassettes do not have: spaces, file extensions, unaccepted characters
(slashes).
an object of class Cassette
Default values for arguments controlling cassette behavior are
inherited from vcr's global configuration. See vcr_configure()
for a
complete list of options and their default settings. You can override these
options for a specific cassette by changing an argument's value to something
other than NULL
when calling either insert_cassette()
or
use_cassette()
.
use_cassette()
, eject_cassette()
## Not run: library(vcr) library(crul) vcr_configure(dir = tempdir()) webmockr::webmockr_allow_net_connect() (x <- insert_cassette(name = "leo5")) current_cassette() x$new_recorded_interactions x$previously_recorded_interactions() cli <- crul::HttpClient$new(url = "https://hb.opencpu.org") cli$get("get") x$new_recorded_interactions # 1 interaction x$previously_recorded_interactions() # empty webmockr::stub_registry() # not empty # very important when using inject_cassette: eject when done x$eject() # same as eject_cassette("leo5") x$new_recorded_interactions # same, 1 interaction x$previously_recorded_interactions() # now not empty ## stub_registry now empty, eject() calls webmockr::disable(), which ## calls the disable method for each of crul and httr adadapters, ## which calls webmockr's remove_stubs() method for each adapter webmockr::stub_registry() # cleanup unlink(file.path(tempdir(), "leo5.yml")) ## End(Not run)
## Not run: library(vcr) library(crul) vcr_configure(dir = tempdir()) webmockr::webmockr_allow_net_connect() (x <- insert_cassette(name = "leo5")) current_cassette() x$new_recorded_interactions x$previously_recorded_interactions() cli <- crul::HttpClient$new(url = "https://hb.opencpu.org") cli$get("get") x$new_recorded_interactions # 1 interaction x$previously_recorded_interactions() # empty webmockr::stub_registry() # not empty # very important when using inject_cassette: eject when done x$eject() # same as eject_cassette("leo5") x$new_recorded_interactions # same, 1 interaction x$previously_recorded_interactions() # now not empty ## stub_registry now empty, eject() calls webmockr::disable(), which ## calls the disable method for each of crul and httr adadapters, ## which calls webmockr's remove_stubs() method for each adapter webmockr::stub_registry() # cleanup unlink(file.path(tempdir(), "leo5.yml")) ## End(Not run)
Turn vcr on and off, check on/off status, and turn off for a given http call
turned_off(..., ignore_cassettes = FALSE) turn_on() turned_on() turn_off(ignore_cassettes = FALSE)
turned_off(..., ignore_cassettes = FALSE) turn_on() turned_on() turn_off(ignore_cassettes = FALSE)
... |
Any block of code to run, presumably an http request |
ignore_cassettes |
(logical) Controls what happens when a cassette is
inserted while vcr is turned off. If |
Sometimes you may need to turn off vcr
, either for individual function
calls, individual test blocks, whole test files, or for the entire
package. The following attempts to break down all the options.
vcr
has the following four exported functions:
turned_off()
- Turns vcr off for the duration of a code block
turn_off()
- Turns vcr off completely, so that it no longer handles
every HTTP request
turn_on()
- turns vcr on; the opposite of turn_off()
turned_on()
- Asks if vcr is turned on, returns a boolean
Instead of using the above four functions, you could use environment
variables to achieve the same thing. This way you could enable/disable
vcr
in non-interactive environments such as continuous integration,
Docker containers, or running R non-interactively from the command line.
The full set of environment variables vcr
uses, all of which accept
only TRUE
or FALSE
:
VCR_TURN_OFF
: turn off vcr altogether; set to TRUE
to skip any vcr
usage; default: FALSE
VCR_TURNED_OFF
: set the turned_off
internal package setting; this
does not turn off vcr completely as does VCR_TURN_OFF
does, but
rather is looked at together with VCR_IGNORE_CASSETTES
VCR_IGNORE_CASSETTES
: set the ignore_cassettes
internal package
setting; this is looked at together with VCR_TURNED_OFF
turned_off()
lets you temporarily make a real HTTP request without
completely turning vcr
off, unloading it, etc.
What happens internally is we turn off vcr
, run your code block, then
on exit turn vcr
back on - such that vcr
is only turned off for the
duration of your code block. Even if your code block errors, vcr
will
be turned back on due to use of on.exit(turn_on())
library(vcr) library(crul) turned_off({ con <- HttpClient$new(url = "https://httpbin.org/get") con$get() })
#> <crul response> #> url: https://httpbin.org/get #> request_headers: #> User-Agent: libcurl/7.54.0 r-curl/4.3 crul/0.9.0 #> Accept-Encoding: gzip, deflate #> Accept: application/json, text/xml, application/xml, */* #> response_headers: #> status: HTTP/1.1 200 OK #> date: Fri, 14 Feb 2020 19:44:46 GMT #> content-type: application/json #> content-length: 365 #> connection: keep-alive #> server: gunicorn/19.9.0 #> access-control-allow-origin: * #> access-control-allow-credentials: true #> status: 200
turn_off()
is different from turned_off()
in that turn_off()
is
not aimed at a single call block, but rather it turns vcr
off for the
entire package. turn_off()
does check first before turning vcr
off
that there is not currently a cassette in use. turn_off()
is meant to
make R ignore vcr::insert_cassette()
and vcr::use_cassette()
blocks
in your test suite - letting the code in the block run as if they were
not wrapped in vcr
code - so that all you have to do to run your tests
with cached requests/responses AND with real HTTP requests is toggle a
single R function or environment variable.
library(vcr) vcr_configure(dir = tempdir()) # real HTTP request works - vcr is not engaged here crul::HttpClient$new(url = "https://eu.httpbin.org/get")$get() # wrap HTTP request in use_cassette() - vcr is engaged here use_cassette("foo_bar", { crul::HttpClient$new(url = "https://eu.httpbin.org/get")$get() }) # turn off & ignore cassettes - use_cassette is ignored, real HTTP request made turn_off(ignore_cassettes = TRUE) use_cassette("foo_bar", { crul::HttpClient$new(url = "https://eu.httpbin.org/get")$get() }) # if you turn off and don't ignore cassettes, error thrown turn_off(ignore_cassettes = FALSE) use_cassette("foo_bar", { res2=crul::HttpClient$new(url = "https://eu.httpbin.org/get")$get() }) # vcr back on - now use_cassette behaves as before turn_on() use_cassette("foo_bar3", { res2=crul::HttpClient$new(url = "https://eu.httpbin.org/get")$get() })
turned_on()
does what it says on the tin - it tells you if vcr
is
turned on or not.
library(vcr) turn_on() turned_on()
## [1] TRUE
turn_off()
## vcr turned off; see ?turn_on to turn vcr back on
turned_on()
## [1] FALSE
The VCR_TURN_OFF
environment variable can be used within R or on the
command line to turn off vcr
. For example, you can run tests for a
package that uses vcr
, but ignore any use_cassette
/insert_cassette
usage, by running this on the command line in the root of your package:
VCR_TURN_OFF=true Rscript -e "devtools::test()"
Or, similarly within R:
Sys.setenv(VCR_TURN_OFF = TRUE) devtools::test()
The VCR_TURNED_OFF
and VCR_IGNORE_CASSETTES
environment variables
can be used in combination to achieve the same thing as VCR_TURN_OFF
:
VCR_TURNED_OFF=true VCR_IGNORE_CASSETTES=true Rscript -e "devtools::test()"
## Not run: vcr_configure(dir = tempdir()) turn_on() turned_on() turn_off() # turn off for duration of a block library(crul) turned_off({ res <- HttpClient$new(url = "https://hb.opencpu.org/get")$get() }) res # turn completely off turn_off() library(webmockr) crul::mock() # HttpClient$new(url = "https://hb.opencpu.org/get")$get(verbose = TRUE) turn_on() ## End(Not run)
## Not run: vcr_configure(dir = tempdir()) turn_on() turned_on() turn_off() # turn off for duration of a block library(crul) turned_off({ res <- HttpClient$new(url = "https://hb.opencpu.org/get")$get() }) res # turn completely off turn_off() library(webmockr) crul::mock() # HttpClient$new(url = "https://hb.opencpu.org/get")$get(verbose = TRUE) turn_on() ## End(Not run)
Are real http connections allowed?
real_http_connections_allowed()
real_http_connections_allowed()
boolean, TRUE
if real HTTP requests allowed; FALSE
if not
real_http_connections_allowed()
real_http_connections_allowed()
vcr recording options
Record modes dictate under what circumstances http requests/responses
are recorded to cassettes (disk). Set the recording mode with the
parameter record
in the use_cassette()
and insert_cassette()
functions.
The once
record mode will:
Replay previously recorded interactions.
Record new interactions if there is no cassette file.
Cause an error to be raised for new requests if there is a cassette file.
It is similar to the new_episodes
record mode, but will prevent new,
unexpected requests from being made (i.e. because the request URI
changed or whatever).
once
is the default record mode, used when you do not set one.
The none
record mode will:
Replay previously recorded interactions.
Cause an error to be raised for any new requests.
This is useful when your code makes potentially dangerous HTTP requests.
The none
record mode guarantees that no new HTTP requests will be
made.
The new_episodes
record mode will:
Record new interactions.
Replay previously recorded interactions.
It is similar to the once
record mode, but will always record new
interactions, even if you have an existing recorded one that is similar
(but not identical, based on the match_request_on
option).
The all
record mode will:
Record new interactions.
Never replay previously recorded interactions.
This can be temporarily used to force vcr to re-record a cassette (i.e. to ensure the responses are not out of date) or can be used when you simply want to log all HTTP requests.
There are a number of options, some of which are on by default, some of which can be used together, and some alone.
To match previously recorded requests, vcr
has to try to match new
HTTP requests to a previously recorded one. By default, we match on HTTP
method (e.g., GET
) and URI (e.g., http://foo.com
), following Ruby’s
VCR gem.
You can customize how we match requests with one or more of the following options, some of which are on by default, some of which can be used together, and some alone.
method
: Use the method request matcher to match requests on the
HTTP method (i.e. GET, POST, PUT, DELETE, etc). You will generally
want to use this matcher. The method matcher is used (along with
the uri matcher) by default if you do not specify how requests
should match.
uri
: Use the uri request matcher to match requests on the
request URI. The uri matcher is used (along with the method
matcher) by default if you do not specify how requests should match.
host
: Use the host request matcher to match requests on the
request host. You can use this (alone, or in combination with
path) as an alternative to uri so that non-deterministic
portions of the URI are not considered as part of the request
matching.
path
: Use the path request matcher to match requests on the path
portion of the request URI. You can use this (alone, or in combination
with host) as an alternative to uri so that non-deterministic
portions of the URI
query
: Use the query request matcher to match requests on the
query string portion of the request URI. You can use this (alone, or
in combination with others) as an alternative to uri so that
non-deterministic portions of the URI are not considered as part of
the request matching.
body
: Use the body request matcher to match requests on the
request body.
headers
: Use the headers request matcher to match requests on
the request headers.
You can set your own options by tweaking the match_requests_on
parameter in use_cassette()
:
library(vcr)
use_cassette(name = "foo_bar", { cli$post("post", body = list(a = 5)) }, match_requests_on = c('method', 'headers', 'body') )
library(crul) library(vcr) cli <- crul::HttpClient$new("https://httpbin.org/get", headers = list(foo = "bar")) use_cassette(name = "nothing_new", { one <- cli$get() }, match_requests_on = 'headers' ) cli$headers$foo <- "stuff" use_cassette(name = "nothing_new", { two <- cli$get() }, match_requests_on = 'headers' ) one$request_headers two$request_headers
Base handler for http requests, deciding whether a request is stubbed, to be ignored, recordable, or unhandled
Private Methods
request_type(request)
Get the request type
externally_stubbed()
just returns FALSE
should_ignore()
should we ignore the request, depends on request ignorer infrastructure that's not working yet
has_response_stub()
Check if there is a matching response stub in the http interaction list
get_stubbed_response()
Check for a response and get it
request_summary(request)
get a request summary
on_externally_stubbed_request(request)
on externally stubbed request do nothing
on_ignored_request(request)
on ignored request, do something
on_recordable_request(request)
on recordable request, record the request
on_unhandled_request(request)
on unhandled request, run UnhandledHTTPRequestError
request_original
original, before any modification
request
the request, after any modification
vcr_response
holds VcrResponse object
stubbed_response
the stubbed response
cassette
the cassette holder
new()
Create a new RequestHandler
object
RequestHandler$new(request)
request
The request from an object of class HttpInteraction
A new RequestHandler
object
handle()
Handle the request (request
given in $initialize()
)
RequestHandler$handle()
handles a request, outcomes vary
clone()
The objects of this class are cloneable with this method.
RequestHandler$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: # record mode: once vcr_configure( dir = tempdir(), record = "once" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request x <- RequestHandler$new(crul_request) # x$handle() # record mode: none vcr_configure( dir = tempdir(), record = "none" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request insert_cassette("testing_record_mode_none", record = "none") #file.path(vcr_c$dir, "testing_record_mode_none.yml") x <- RequestHandlerCrul$new(crul_request) # x$handle() crul_request$url$url <- "https://api.crossref.org/works/10.1039/c8sm90002g/" crul_request$url$handle <- curl::new_handle() z <- RequestHandlerCrul$new(crul_request) # z$handle() eject_cassette("testing_record_mode_none") ## End(Not run)
## Not run: # record mode: once vcr_configure( dir = tempdir(), record = "once" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request x <- RequestHandler$new(crul_request) # x$handle() # record mode: none vcr_configure( dir = tempdir(), record = "none" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request insert_cassette("testing_record_mode_none", record = "none") #file.path(vcr_c$dir, "testing_record_mode_none.yml") x <- RequestHandlerCrul$new(crul_request) # x$handle() crul_request$url$url <- "https://api.crossref.org/works/10.1039/c8sm90002g/" crul_request$url$handle <- curl::new_handle() z <- RequestHandlerCrul$new(crul_request) # z$handle() eject_cassette("testing_record_mode_none") ## End(Not run)
Methods for the crul package, building on RequestHandler
vcr::RequestHandler
-> RequestHandlerCrul
clone()
The objects of this class are cloneable with this method.
RequestHandlerCrul$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: vcr_configure( dir = tempdir(), record = "once" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request x <- RequestHandlerCrul$new(crul_request) # x$handle() # body matching library(vcr) library(crul) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) cli <- HttpClient$new(url = "https://hb.opencpu.org") ## testing, same uri and method, changed body in 2nd block use_cassette(name = "apple7", { resp <- cli$post("post", body = list(foo = "bar")) }, match_requests_on = c("method", "uri", "body")) ## should error, b/c record="once" if (interactive()) { use_cassette(name = "apple7", { resp <- cli$post("post", body = list(foo = "bar")) resp2 <- cli$post("post", body = list(hello = "world")) }, match_requests_on = c("method", "uri", "body")) } cas <- insert_cassette(name = "apple7", match_requests_on = c("method", "uri", "body")) resp2 <- cli$post("post", body = list(foo = "bar")) eject_cassette("apple7") ## testing, same body, changed method in 2nd block if (interactive()) { use_cassette(name = "apple8", { x <- cli$post("post", body = list(hello = "world")) }, match_requests_on = c("method", "body")) use_cassette(name = "apple8", { x <- cli$get("post", body = list(hello = "world")) }, match_requests_on = c("method", "body")) } ## testing, same body, changed uri in 2nd block # use_cassette(name = "apple9", { # x <- cli$post("post", body = list(hello = "world")) # w <- cli$post("get", body = list(hello = "world")) # }, match_requests_on = c("method", "body")) # use_cassette(name = "apple9", { # NOTHING HERE # }, match_requests_on = c("method", "body")) # unlink(file.path(vcr_configuration()$dir, "apple9.yml")) ## End(Not run)
## Not run: vcr_configure( dir = tempdir(), record = "once" ) data(crul_request) crul_request$url$handle <- curl::new_handle() crul_request x <- RequestHandlerCrul$new(crul_request) # x$handle() # body matching library(vcr) library(crul) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) cli <- HttpClient$new(url = "https://hb.opencpu.org") ## testing, same uri and method, changed body in 2nd block use_cassette(name = "apple7", { resp <- cli$post("post", body = list(foo = "bar")) }, match_requests_on = c("method", "uri", "body")) ## should error, b/c record="once" if (interactive()) { use_cassette(name = "apple7", { resp <- cli$post("post", body = list(foo = "bar")) resp2 <- cli$post("post", body = list(hello = "world")) }, match_requests_on = c("method", "uri", "body")) } cas <- insert_cassette(name = "apple7", match_requests_on = c("method", "uri", "body")) resp2 <- cli$post("post", body = list(foo = "bar")) eject_cassette("apple7") ## testing, same body, changed method in 2nd block if (interactive()) { use_cassette(name = "apple8", { x <- cli$post("post", body = list(hello = "world")) }, match_requests_on = c("method", "body")) use_cassette(name = "apple8", { x <- cli$get("post", body = list(hello = "world")) }, match_requests_on = c("method", "body")) } ## testing, same body, changed uri in 2nd block # use_cassette(name = "apple9", { # x <- cli$post("post", body = list(hello = "world")) # w <- cli$post("get", body = list(hello = "world")) # }, match_requests_on = c("method", "body")) # use_cassette(name = "apple9", { # NOTHING HERE # }, match_requests_on = c("method", "body")) # unlink(file.path(vcr_configuration()$dir, "apple9.yml")) ## End(Not run)
Methods for the httr package, building on RequestHandler
vcr::RequestHandler
-> RequestHandlerHttr
new()
Create a new RequestHandlerHttr
object
RequestHandlerHttr$new(request)
request
The request from an object of class HttpInteraction
A new RequestHandlerHttr
object
clone()
The objects of this class are cloneable with this method.
RequestHandlerHttr$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: vcr_configure( dir = tempdir(), record = "once" ) # GET request library(httr) load("~/httr_req.rda") req x <- RequestHandlerHttr$new(req) # x$handle() # POST request library(httr) webmockr::httr_mock() mydir <- file.path(tempdir(), "testing_httr") invisible(vcr_configure(dir = mydir)) use_cassette(name = "testing2", { res <- POST("https://hb.opencpu.org/post", body = list(foo = "bar")) }, match_requests_on = c("method", "uri", "body")) load("~/httr_req_post.rda") insert_cassette("testing3") httr_req_post x <- RequestHandlerHttr$new(httr_req_post) x # x$handle() self=x ## End(Not run)
## Not run: vcr_configure( dir = tempdir(), record = "once" ) # GET request library(httr) load("~/httr_req.rda") req x <- RequestHandlerHttr$new(req) # x$handle() # POST request library(httr) webmockr::httr_mock() mydir <- file.path(tempdir(), "testing_httr") invisible(vcr_configure(dir = mydir)) use_cassette(name = "testing2", { res <- POST("https://hb.opencpu.org/post", body = list(foo = "bar")) }, match_requests_on = c("method", "uri", "body")) load("~/httr_req_post.rda") insert_cassette("testing3") httr_req_post x <- RequestHandlerHttr$new(httr_req_post) x # x$handle() self=x ## End(Not run)
Methods for the httr2 package, building on RequestHandler
vcr::RequestHandler
-> RequestHandlerHttr2
new()
Create a new RequestHandlerHttr2
object
RequestHandlerHttr2$new(request)
request
The request from an object of class HttpInteraction
A new RequestHandlerHttr2
object
clone()
The objects of this class are cloneable with this method.
RequestHandlerHttr2$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: # GET request library(httr2) req <- request("https://hb.opencpu.org/post") %>% req_body_json(list(foo = "bar")) x <- RequestHandlerHttr2$new(req) # x$handle() # POST request library(httr2) mydir <- file.path(tempdir(), "testing_httr2") invisible(vcr_configure(dir = mydir)) req <- request("https://hb.opencpu.org/post") %>% req_body_json(list(foo = "bar")) use_cassette(name = "testing3", { response <- req_perform(req) }, match_requests_on = c("method", "uri", "body")) use_cassette(name = "testing3", { response2 <- req_perform(req) }, match_requests_on = c("method", "uri", "body")) ## End(Not run)
## Not run: # GET request library(httr2) req <- request("https://hb.opencpu.org/post") %>% req_body_json(list(foo = "bar")) x <- RequestHandlerHttr2$new(req) # x$handle() # POST request library(httr2) mydir <- file.path(tempdir(), "testing_httr2") invisible(vcr_configure(dir = mydir)) req <- request("https://hb.opencpu.org/post") %>% req_body_json(list(foo = "bar")) use_cassette(name = "testing3", { response <- req_perform(req) }, match_requests_on = c("method", "uri", "body")) use_cassette(name = "testing3", { response2 <- req_perform(req) }, match_requests_on = c("method", "uri", "body")) ## End(Not run)
handles request matchers
registry
initialze registry list with a request, or leave empty
default_matchers
request matchers to use. default: method, uri
new()
Create a new RequestMatcherRegistry object
RequestMatcherRegistry$new( registry = list(), default_matchers = list("method", "uri") )
registry
initialze registry list with a request, or leave empty
default_matchers
request matchers to use. default: method, uri
A new RequestMatcherRegistry
object
register()
Register a custom matcher
RequestMatcherRegistry$register(name, func)
name
matcher name
func
function that describes a matcher, should return a single boolean
no return; registers the matcher
register_built_ins()
Register all built in matchers
RequestMatcherRegistry$register_built_ins()
no return; registers all built in matchers
try_to_register_body_as_json()
Try to register body as JSON
RequestMatcherRegistry$try_to_register_body_as_json(r1, r2)
r1, r2
Request class objects
no return; registers the matcher
clone()
The objects of this class are cloneable with this method.
RequestMatcherRegistry$clone(deep = FALSE)
deep
Whether to make a deep clone.
r1=from new request; r2=from recorded interaction
## Not run: (x <- RequestMatcherRegistry$new()) x$default_matchers x$registry ## End(Not run)
## Not run: (x <- RequestMatcherRegistry$new()) x$default_matchers x$registry ## End(Not run)
Custom testthat skipper to skip tests if vcr is turned off via the
environment variable VCR_TURN_OFF
.
skip_if_vcr_off()
skip_if_vcr_off()
This might be useful if your test will fail with real requests: when the cassette was e.g. edited (a real request produced a 200 status code but you made it a 502 status code for testing the behavior of your code when the API errors) or if the tests are very specific (e.g. testing a date was correctly parsed, but making a real request would produce a different date).
Nothing, skip test.
split string every N characters
str_splitter(str, length)
str_splitter(str, length)
str |
(character) a string |
length |
(integer) number of characters to split by |
## Not run: str = "XOVEWVJIEWNIGOIWENVOIWEWVWEW" str_splitter(str, 5) str_splitter(str, 5L) ## End(Not run)
## Not run: str = "XOVEWVJIEWNIGOIWENVOIWEWVWEW" str_splitter(str, 5) str_splitter(str, 5L) ## End(Not run)
Handle http request errors
vcr_last_error()
vcr_last_error()
How this error class is used:
If record="once"
we trigger this.
Users can use vcr in the context of both use_cassette()
and insert_cassette()
For the former, all requests go through the call_block But for the latter, requests go through webmockr.
Where is one place where we can put UnhandledHTTPRequestError that will handle both use_cassette and insert_cassette?
record=once
AND there's a new request that doesn't match
the one in the cassette on disk
in webmockr: if no stub found and there are recorded interactions on the cassette, and record = once, then error with UnhandledHTTPRequestError
but if record != once, then allow it, unless record == none
others?
request
a Request object
cassette
a cassette name
new()
Create a new UnhandledHTTPRequestError
object
UnhandledHTTPRequestError$new(request)
request
(Request) a Request object
A new UnhandledHTTPRequestError
object
run()
Run unhandled request handling
UnhandledHTTPRequestError$run()
various
construct_message()
Construct and execute stop message for why request failed
UnhandledHTTPRequestError$construct_message()
a stop message
request_description()
construct request description
UnhandledHTTPRequestError$request_description()
character
current_matchers()
get current request matchers
UnhandledHTTPRequestError$current_matchers()
character
match_request_on_headers()
are headers included in current matchers?
UnhandledHTTPRequestError$match_request_on_headers()
logical
match_request_on_body()
is body includled in current matchers?
UnhandledHTTPRequestError$match_request_on_body()
logical
formatted_headers()
get request headers
UnhandledHTTPRequestError$formatted_headers()
character
cassettes_description()
construct description of current or lack thereof cassettes
UnhandledHTTPRequestError$cassettes_description()
character
cassettes_list()
cassette details
UnhandledHTTPRequestError$cassettes_list()
character
get_help()
get help message for non-verbose error
UnhandledHTTPRequestError$get_help()
character
formatted_suggestions()
make suggestions for what to do
UnhandledHTTPRequestError$formatted_suggestions()
character
format_bullet_point()
add bullet point to beginning of a line
UnhandledHTTPRequestError$format_bullet_point(lines, index)
lines
(character) vector of strings
index
(integer) a number
character
format_foot_note()
make a foot note
UnhandledHTTPRequestError$format_foot_note(url, index)
url
(character) a url
index
(integer) a number
character
suggestion_for()
get a suggestion by key
UnhandledHTTPRequestError$suggestion_for(key)
key
(character) a character string
character
suggestions()
get all suggestions
UnhandledHTTPRequestError$suggestions()
list
no_cassette_suggestions()
get all no cassette suggestions
UnhandledHTTPRequestError$no_cassette_suggestions()
list
record_mode_suggestion()
get the appropriate record mode suggestion
UnhandledHTTPRequestError$record_mode_suggestion()
character
has_used_interaction_matching()
are there any used interactions
UnhandledHTTPRequestError$has_used_interaction_matching()
logical
match_requests_on_suggestion()
match requests on suggestion
UnhandledHTTPRequestError$match_requests_on_suggestion()
list
clone()
The objects of this class are cloneable with this method.
UnhandledHTTPRequestError$clone(deep = FALSE)
deep
Whether to make a deep clone.
## Not run: vcr_configure(dir = tempdir()) cassettes() insert_cassette("turtle") request <- Request$new("post", 'https://hb.opencpu.org/post?a=5', "", list(foo = "bar")) err <- UnhandledHTTPRequestError$new(request) err$request_description() err$current_matchers() err$match_request_on_headers() err$match_request_on_body() err$formatted_headers() cat(err$formatted_headers(), "\n") cat(err$cassettes_description(), "\n") cat(err$cassettes_list(), "\n") err$formatted_suggestions() cat(err$format_bullet_point('foo bar', 1), "\n") err$suggestion_for("use_new_episodes") err$suggestions() err$no_cassette_suggestions() err$record_mode_suggestion() err$has_used_interaction_matching() err$match_requests_on_suggestion() # err$construct_message() # cleanup eject_cassette("turtle") unlink(tempdir()) ## End(Not run) ## Not run: # vcr_last_error() ## End(Not run)
## Not run: vcr_configure(dir = tempdir()) cassettes() insert_cassette("turtle") request <- Request$new("post", 'https://hb.opencpu.org/post?a=5', "", list(foo = "bar")) err <- UnhandledHTTPRequestError$new(request) err$request_description() err$current_matchers() err$match_request_on_headers() err$match_request_on_body() err$formatted_headers() cat(err$formatted_headers(), "\n") cat(err$cassettes_description(), "\n") cat(err$cassettes_list(), "\n") err$formatted_suggestions() cat(err$format_bullet_point('foo bar', 1), "\n") err$suggestion_for("use_new_episodes") err$suggestions() err$no_cassette_suggestions() err$record_mode_suggestion() err$has_used_interaction_matching() err$match_requests_on_suggestion() # err$construct_message() # cleanup eject_cassette("turtle") unlink(tempdir()) ## End(Not run) ## Not run: # vcr_last_error() ## End(Not run)
Use a cassette to record HTTP requests
use_cassette( name, ..., record = NULL, match_requests_on = NULL, update_content_length_header = FALSE, allow_playback_repeats = FALSE, serialize_with = NULL, persist_with = NULL, preserve_exact_body_bytes = NULL, re_record_interval = NULL, clean_outdated_http_interactions = NULL )
use_cassette( name, ..., record = NULL, match_requests_on = NULL, update_content_length_header = FALSE, allow_playback_repeats = FALSE, serialize_with = NULL, persist_with = NULL, preserve_exact_body_bytes = NULL, re_record_interval = NULL, clean_outdated_http_interactions = NULL )
name |
The name of the cassette. vcr will check this to ensure it
is a valid file name. Not allowed: spaces, file extensions, control
characters (e.g., |
... |
a block of code containing one or more requests (required). Use
curly braces to encapsulate multi-line code blocks. If you can't pass a code
block use |
record |
The record mode (default: |
match_requests_on |
List of request matchers
to use to determine what recorded HTTP interaction to replay. Defaults to
|
update_content_length_header |
(logical) Whether or
not to overwrite the |
allow_playback_repeats |
(logical) Whether or not to
allow a single HTTP interaction to be played back multiple times.
Default: |
serialize_with |
(character) Which serializer to use. Valid values are "yaml" (default) and "json". Note that you can have multiple cassettes with the same name as long as they use different serializers; so if you only want one cassette for a given cassette name, make sure to not switch serializers, or clean up files you no longer need. |
persist_with |
(character) Which cassette persister to use. Default: "file_system". You can also register and use a custom persister. |
preserve_exact_body_bytes |
(logical) Whether or not
to base64 encode the bytes of the requests and responses for
this cassette when serializing it. See also |
re_record_interval |
(integer) How frequently (in seconds) the
cassette should be re-recorded. default: |
clean_outdated_http_interactions |
(logical) Should outdated
interactions be recorded back to file? default: |
A run down of the family of top level vcr functions
use_cassette
Initializes a cassette. Returns the inserted
cassette.
insert_cassette
Internally used within use_cassette
eject_cassette
ejects the current cassette. The cassette
will no longer be used. In addition, any newly recorded HTTP interactions
will be written to disk.
an object of class Cassette
Default values for arguments controlling cassette behavior are
inherited from vcr's global configuration. See vcr_configure()
for a
complete list of options and their default settings. You can override these
options for a specific cassette by changing an argument's value to something
other than NULL
when calling either insert_cassette()
or
use_cassette()
.
This function handles a few different scenarios:
when everything runs smoothly, and we return a Cassette
class object
so you can inspect the cassette, and the cassette is ejected
when there is an invalid parameter input on cassette creation, we fail with a useful message, we don't return a cassette, and the cassette is ejected
when there is an error in calling your passed in code block,
we return with a useful message, and since we use on.exit()
the cassette is still ejected even though there was an error,
but you don't get an object back
whenever an empty cassette (a yml/json file) is found, we delete it
before returning from the use_cassette()
function call. we achieve
this via use of on.exit()
so an empty cassette is deleted even
if there was an error in the code block you passed in
Note that "eject" only means that the R session cassette is no longer in use. If any interactions were recorded to disk, then there is a file on disk with those interactions.
There's a few ways to get correct line numbers for failed tests and one way to not get correct line numbers:
Correct: Either wrap your test_that()
block inside your use_cassette()
block, OR if you put your use_cassette()
block inside your test_that()
block put your testthat
expectations outside of the use_cassette()
block.
Incorrect: By wrapping the use_cassette()
block inside your
test_that()
block with your testthat expectations inside the
use_cassette()
block, you'll only get the line number that the
use_cassette()
block starts on.
insert_cassette()
, eject_cassette()
## Not run: library(vcr) library(crul) vcr_configure(dir = tempdir()) use_cassette(name = "apple7", { cli <- HttpClient$new(url = "https://hb.opencpu.org") resp <- cli$get("get") }) readLines(file.path(tempdir(), "apple7.yml")) # preserve exact body bytes - records in base64 encoding use_cassette("things4", { cli <- crul::HttpClient$new(url = "https://hb.opencpu.org") bbb <- cli$get("get") }, preserve_exact_body_bytes = TRUE) ## see the body string value in the output here readLines(file.path(tempdir(), "things4.yml")) # cleanup unlink(file.path(tempdir(), c("things4.yml", "apple7.yml"))) # with httr library(vcr) library(httr) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) use_cassette(name = "stuff350", { res <- GET("https://hb.opencpu.org/get") }) readLines(file.path(tempdir(), "stuff350.yml")) use_cassette(name = "catfact456", { res <- GET("https://catfact.ninja/fact") }) # record mode: none library(crul) vcr_configure(dir = tempdir()) ## make a connection first conn <- crul::HttpClient$new("https://eu.httpbin.org") ## this errors because 'none' disallows any new requests # use_cassette("none_eg", (res2 <- conn$get("get")), record = "none") ## first use record mode 'once' to record to a cassette one <- use_cassette("none_eg", (res <- conn$get("get")), record = "once") one; res ## then use record mode 'none' to see it's behavior two <- use_cassette("none_eg", (res2 <- conn$get("get")), record = "none") two; res2 ## End(Not run)
## Not run: library(vcr) library(crul) vcr_configure(dir = tempdir()) use_cassette(name = "apple7", { cli <- HttpClient$new(url = "https://hb.opencpu.org") resp <- cli$get("get") }) readLines(file.path(tempdir(), "apple7.yml")) # preserve exact body bytes - records in base64 encoding use_cassette("things4", { cli <- crul::HttpClient$new(url = "https://hb.opencpu.org") bbb <- cli$get("get") }, preserve_exact_body_bytes = TRUE) ## see the body string value in the output here readLines(file.path(tempdir(), "things4.yml")) # cleanup unlink(file.path(tempdir(), c("things4.yml", "apple7.yml"))) # with httr library(vcr) library(httr) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) use_cassette(name = "stuff350", { res <- GET("https://hb.opencpu.org/get") }) readLines(file.path(tempdir(), "stuff350.yml")) use_cassette(name = "catfact456", { res <- GET("https://catfact.ninja/fact") }) # record mode: none library(crul) vcr_configure(dir = tempdir()) ## make a connection first conn <- crul::HttpClient$new("https://eu.httpbin.org") ## this errors because 'none' disallows any new requests # use_cassette("none_eg", (res2 <- conn$get("get")), record = "none") ## first use record mode 'once' to record to a cassette one <- use_cassette("none_eg", (res <- conn$get("get")), record = "once") one; res ## then use record mode 'none' to see it's behavior two <- use_cassette("none_eg", (res2 <- conn$get("get")), record = "none") two; res2 ## End(Not run)
Setup vcr for a package
use_vcr(dir = ".", verbose = TRUE)
use_vcr(dir = ".", verbose = TRUE)
dir |
(character) path to package root. default's to current directory |
verbose |
(logical) print progress messages. default: |
Sets a mimimum vcr version, which is usually the latest (stable) version on CRAN. You can of course easily remove or change the version requirement yourself after running this function.
only messages about progress, returns invisible()
Configurable options that define vcr's default behavior.
vcr_configure(...) vcr_configure_reset() vcr_configuration() vcr_config_defaults()
vcr_configure(...) vcr_configure_reset() vcr_configuration() vcr_config_defaults()
... |
configuration settings used to override defaults. See below for a complete list of valid arguments. |
dir
Cassette directory
write_disk_path
(character) path to write files to
for any requests that write responses to disk. by default this parameter
is NULL
. For testing a package, you'll probably want this path to
be in your tests/
directory, perhaps next to your cassettes
directory, e.g., where your cassettes are in tests/fixtures
, your
files from requests that write to disk are in tests/files
.
If you want to ignore these files in your installed package,
add them to .Rinstignore
. If you want these files ignored on build
then add them to .Rbuildignore
(though if you do, tests that depend
on these files probably will not work because they won't be found; so
you'll likely have to skip the associated tests as well).
turned_off
(logical) VCR is turned on by default. Default:
FALSE
allow_unused_http_interactions
(logical) Default: TRUE
allow_http_connections_when_no_cassette
(logical) Determines how vcr
treats HTTP requests that are made when no vcr cassette is in use. When
TRUE
, requests made when there is no vcr cassette in use will be allowed.
When FALSE
(default), an UnhandledHTTPRequestError error will be raised
for any HTTP request made when there is no cassette in use
ignore_hosts
(character) Vector of hosts to ignore. e.g., localhost, or
google.com. These hosts are ignored and real HTTP requests allowed to go
through
ignore_localhost
(logical) Default: FALSE
ignore_request
List of requests to ignore. NOT USED RIGHT NOW, sorry
filter_sensitive_data
named list of values to replace. Format is:
list(thing_to_replace_it_with = thing_to_replace)
We replace all instances of thing_to_replace
with
thing_to_replace_it_with
. Uses gsub()
internally, with fixed=TRUE
;
so does exact matches. Before recording (writing to a cassette) we do
the replacement and then when reading from the cassette we do the reverse
replacement to get back to the real data. Before record replacement happens
in internal function write_interactions()
, while before playback
replacement happens in internal function YAML$deserialize()
filter_sensitive_data_regex
named list of values to replace. Follows
filter_sensitive_data
format, except uses fixed=FALSE
in the gsub()
function call; this means that the value in thing_to_replace
is a regex
pattern.
filter_request_headers
(character/list) request headers to filter.
A character vector of request headers to remove - the headers will not be
recorded to disk. Alternatively, a named list similar to
filter_sensitive_data
instructing vcr with what value to replace the
real value of the request header.
filter_response_headers
(named list) response headers to filter.
A character vector of response headers to remove - the headers will not be
recorded to disk. Alternatively, a named list similar to
filter_sensitive_data
instructing vcr with what value to replace the
real value of the response header.
filter_query_parameters
(named list) query parameters to filter.
A character vector of query parameters to remove - the query parameters
will not be recorded to disk. Alternatively, a named list similar to
filter_sensitive_data
instructing vcr with what value to replace the
real value of the query parameter.
verbose_errors
Do you want more verbose errors or less verbose
errors when cassette recording/usage fails? Default is FALSE
, that is,
less verbose errors. If TRUE
, error messages will include more details
about what went wrong and suggest possible solutions. For testing
in an interactive R session, if verbose_errors=FALSE
, you can run
vcr_last_error()
to get the full error. If in non-interactive mode,
which most users will be in when running the entire test suite for a
package, you can set an environment variable (VCR_VERBOSE_ERRORS
)
to toggle this setting (e.g.,
Sys.setenv(VCR_VERBOSE_ERRORS=TRUE); devtools::test()
)
cassettes
(list) don't use
linked_context
(logical) linked context
uri_parser
the uri parser, default: crul::url_parse()
log
(logical) should we log important vcr things? Default: FALSE
log_opts
(list) Additional logging options:
'file' either "console"
or a file path to log to
'log_prefix' default: "Cassette". We insert the cassette name after that prefix, then the rest of the message.
More to come...
These settings can be configured globally, using vcr_configure()
, or
locally, using either use_cassette()
or insert_cassette()
. Global
settings are applied to all cassettes but are overridden by settings
defined locally for individual cassettes.
record
(character) One of 'all', 'none', 'new_episodes', or 'once'.
See recording
match_requests_on
vector of matchers. Default: (method
, uri
)
See request-matching for details.
serialize_with
: (character) "yaml" or "json". Note that you can have
multiple cassettes with the same name as long as they use different
serializers; so if you only want one cassette for a given cassette name,
make sure to not switch serializers, or clean up files you no longer need.
json_pretty
: (logical) want JSON to be newline separated to be easier
to read? Or remove newlines to save disk space? default: FALSE
persist_with
(character) only option is "FileSystem"
preserve_exact_body_bytes
(logical) preserve exact body bytes for
re_record_interval
(numeric) When given, the cassette will be
re-recorded at the given interval, in seconds.
clean_outdated_http_interactions
(logical) Should outdated interactions
be recorded back to file. Default: FALSE
quiet
(logical) Suppress any messages from both vcr and webmockr.
Default: TRUE
warn_on_empty_cassette
(logical) Should a warning be thrown when an
empty cassette is detected? Empty cassettes are cleaned up (deleted) either
way. This option only determines whether a warning is thrown or not.
Default: FALSE
vcr_configure(dir = tempdir()) vcr_configure(dir = tempdir(), record = "all") vcr_configuration() vcr_config_defaults() vcr_configure(dir = tempdir(), ignore_hosts = "google.com") vcr_configure(dir = tempdir(), ignore_localhost = TRUE) # logging vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = "console")) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list( file = file.path(tempdir(), "vcr.log"), log_prefix = "foobar" )) vcr_configure(dir = tempdir(), log = FALSE) # filter sensitive data vcr_configure(dir = tempdir(), filter_sensitive_data = list(foo = "<bar>") ) vcr_configure(dir = tempdir(), filter_sensitive_data = list(foo = "<bar>", hello = "<world>") )
vcr_configure(dir = tempdir()) vcr_configure(dir = tempdir(), record = "all") vcr_configuration() vcr_config_defaults() vcr_configure(dir = tempdir(), ignore_hosts = "google.com") vcr_configure(dir = tempdir(), ignore_localhost = TRUE) # logging vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = file.path(tempdir(), "vcr.log"))) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list(file = "console")) vcr_configure(dir = tempdir(), log = TRUE, log_opts = list( file = file.path(tempdir(), "vcr.log"), log_prefix = "foobar" )) vcr_configure(dir = tempdir(), log = FALSE) # filter sensitive data vcr_configure(dir = tempdir(), filter_sensitive_data = list(foo = "<bar>") ) vcr_configure(dir = tempdir(), filter_sensitive_data = list(foo = "<bar>", hello = "<world>") )
This function, similar to testthat::test_path()
, is designed to work both
interactively and during tests, locating files in the tests/
directory.
vcr_test_path(...)
vcr_test_path(...)
... |
Character vectors giving path component. each character string
gets added on to the path, e.g., |
A character vector giving the path
vcr_test_path()
assumes you are using testthat for your unit tests.
if (interactive()) { vcr_test_path("fixtures") }
if (interactive()) { vcr_test_path("fixtures") }