knitr::opts_chunk$set( collapse = TRUE, comment = "#>", out.width = "100%" )
With version 3.0.0 of testthat, mocking capabilities provided by testthat::with_mock() and testthat::local_mock() have been deprecated under edition 3. This leaves implementation of function mocking for unit testing to third-party packages, of which two have been published on CRAN: mockery and mockr. While all currently available mocking implementations have their limitations, what sets mockthat apart from mockery and mockr is coping with S3 dispatch (see example below).
You can install the development version of mockthat from GitHub with:
# install.packages("devtools") devtools::install_github("nbenn/mockthat")
A release version will be submitted to CRAN shortly.
Mocking in the context of unit testing refers to temporarily replacing a piece of functionality (that might be part of the package being tested or potentially even part of a downstream dependency) in order to cope with limited infrastructure in testing environments (for example absence of a live Internet connection).
For a function download_data() implemented as
download_data <- function(url) { curl::curl_fetch_memory(url) }
we do not want to have to rely on a live internet connection for writing a unit test. With help of mockthat, we can substitute curl::curl_fetch_memory() with a stub that simply returns a
constant.
library(mockthat) url <- "https://eu.httpbin.org/get?foo=123" with_mock( `curl::curl_fetch_memory` = function(...) '["mocked request"]', download_data(url) )
As mentioned above, the main point of differentiation of mockthat over the other available packages mockery and mockr is stubbing out functions in the context of S3 dispatch. Assuming the following set-up,
gen <- function(x) UseMethod("gen") met <- function(x) foo(x) foo <- function(x) stop("foo") .S3method("gen", "cls", met) x <- structure(123, class = "cls") gen(x)
mockthat::with_mock() can be used to catch the call to foo() and therefore prevent the error from being thrown.
mockthat::with_mock( foo = function(x) "bar", met(x) )
This is not possible with the current implementation of mockr::with_mock().
mockr::with_mock( foo = function(x) "bar", met(x) )
And with the current API of mockery::stub() it is unclear how the depth argument should be chosen, as the function gen() does not contain a call to met(). Trying a range of sensible values does not yield the desired result.
for (depth in seq_len(3L)) { mockery::stub(gen, "foo", "bar", depth = depth) tryCatch(met(x), error = function(e) message("depth ", depth, ": nope")) }
Borrowing from mockery, mockthat also allows for creating mock objects (with class attribute mock_fun), which allow capture of the call for later examination.
mk <- mock("mocked request") dl <- function(url) curl::curl(url) with_mock(`curl::curl` = mk, dl(url)) mock_call(mk) mock_args(mk)
In addition to with_mock(), mockthat also offers a local_mock() function,
again, mimicking the deprecated testthat function, which keeps the mocks in place for the life-time of the environment passed as local_env argument (or if called from the global environment, until withr::deferred_run() is executed). Mock objects as shown above are created (and returned invisibly) for all non-function objects passed as ....
tmp <- new.env() mk <- local_mock(`curl::curl` = "mocked request", local_env = tmp) dl(url) mock_arg(mk, "url")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.