Mocking allows you to temporary replace the implementation of functions within a package, which useful for testing code that relies on functions that are slow, have unintended side effects or access resources that may not be available when testing.
Up until recently, such capability was offered via testthat::with_mock()
,
but with release of version 3.0.0 and introduction of edition 3, this was
deprecated from 'testthat', leaving it to third party packages to replace
this feature. Powered by utils::assignInNamespace()
, this mocking
implementation can be used to stub out both exported and non-exported
functions from a package, as well as functions explicitly imported from
other packages using either importFrom
directives or namespaced function
calls using ::
.
with_mock(..., mock_env = pkg_env(), eval_env = parent.frame())
local_mock(
...,
mock_env = pkg_env(),
eval_env = parent.frame(),
local_env = eval_env
)
Named parameters redefine mocked functions, unnamed parameters will be evaluated after mocking the functions.
The environment in which to patch the functions,
defaults to either the package namespace when the environment variable
TESTTHAT_PKG
is set pr the calling environment. A string is interpreted
as package
name.
Environment in which expressions passed as ...
are
evaluated, defaults to base::parent.frame()
.
Passed to withr::defer()
as envir
argument (defaults
to the values passed as eval_env
)
The result of the last unnamed argument passed as ...
(evaluated in the
environment passed as eval_env
) in the case of local_mock()
and a list
of functions or mock_fun
objects (invisibly) for calls to local_mock()
.
Borrowing the API from the now-deprecated testthat::with_mock()
, named
arguments passed as ...
are used to define functions to be mocked, where
names specify the target functions and the arguments themselves are used as
replacement functions. Unnamed arguments passed as ...
will be evaluated
in the environment specified as eval_env
using the mocked functions.
Functions to be stubbed should be specified as they would be used in package
core. This means that when a function from a third party package is
imported, prefixing the function name with pkg_name::
will not give the
desired result. Conversely, if the function is not imported, the package
prefix is of course required. On exit of with_mock()
, the mocked functions
are reverted to their original state.
Replacement functions can either be specified as complete functions, or as
either quoted expressions, subsequently used as function body or objects
used as return values. If functions are created from return values or
complete function bodies, they inherit the signatures from the respective
functions they are used to mock, alongside the ability to keep track of
how they are subsequently called. A constructor for such mock-objects is
available as mock()
, which quotes the expression passed as expr
.
If mocking is desirable for multiple separate calls to the function being
tested, local_mock()
is available, which holds onto the mocked state for
the lifetime of the environment passed as local_env
using
withr::defer()
. Unlike with_mock()
, which returns the result of
evaluating the last unnamed argument passed as ...
, local_mock()
(invisibly) returns the functions used for mocking, which if not fully
specified as functions, will be mock-objects described in the previous
paragraph.
url <- "https://eu.httpbin.org/get?foo=123"
mok <- function(...) "mocked request"
with_mock(
`curl::curl_fetch_memory` = mok,
curl::curl_fetch_memory(url)
)
#> [1] "mocked request"
dl_fun <- function(x) curl::curl_fetch_memory(x)
with_mock(
`curl::curl_fetch_memory` = mok,
dl_fun(url)
)
#> [1] "mocked request"
with_mock(
`curl::curl_fetch_memory` = "mocked request",
dl_fun(url)
)
#> [1] "mocked request"
dl <- function(x) curl::curl(x)
local({
mk <- local_mock(`curl::curl` = "mocked request")
list(dl(url), mock_arg(mk, "url"))
})
#> [[1]]
#> [1] "mocked request"
#>
#> [[2]]
#> [1] "https://eu.httpbin.org/get?foo=123"
#>