-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathREADME.Rmd
128 lines (92 loc) · 5.12 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
out.width = "100%"
)
```
# mockthat
<!-- badges: start -->
[![Lifecycle](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable)
[![R build status](https://github.com/nbenn/mockthat/workflows/build/badge.svg)](https://github.com/nbenn/mockthat/actions?query=workflow%3Abuild)
[![R check status](https://github.com/nbenn/mockthat/workflows/check/badge.svg)](https://github.com/nbenn/mockthat/actions?query=workflow%3Acheck)
[![pkgdown build status](https://github.com/nbenn/mockthat/workflows/pkgdown/badge.svg)](https://github.com/nbenn/mockthat/actions?query=workflow%3Apkgdown)
[![covr status](https://github.com/nbenn/mockthat/workflows/coverage/badge.svg)](https://github.com/nbenn/mockthat/actions?query=workflow%3Acoverage)
[![Codecov test coverage](https://codecov.io/gh/nbenn/mockthat/branch/master/graph/badge.svg?token=9v2gSCz5K5)](https://app.codecov.io/gh/nbenn/mockthat)
<!-- badges: end -->
With version 3.0.0 of `testthat`, mocking capabilities provided by [`testthat::with_mock()`](https://testthat.r-lib.org/reference/with_mock.html) and [`testthat::local_mock()`](https://testthat.r-lib.org/reference/with_mock.html) 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`](https://cran.r-project.org/package=mockery) and [`mockr`](https://cran.r-project.org/package=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).
## Installation
You can install the development version of `mockthat` from [GitHub](https://github.com/) with:
``` r
# install.packages("devtools")
devtools::install_github("nbenn/mockthat")
```
A release version will be submitted to [CRAN](https://CRAN.R-project.org) shortly.
## Example
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
```{r fun-setup}
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()`](https://rdrr.io/cran/curl/man/curl_fetch.html) with a stub that simply returns a
constant.
```{r fun-example}
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,
```{r s3-setup, error = TRUE}
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.
```{r s3-mockthat}
mockthat::with_mock(
foo = function(x) "bar",
met(x)
)
```
This is not possible with the current implementation of [`mockr::with_mock()`](https://krlmlr.github.io/mockr/reference/local_mock.html).
```{r s3-mockr, error = TRUE}
mockr::with_mock(
foo = function(x) "bar",
met(x)
)
```
And with the current API of [`mockery::stub()`](https://rdrr.io/cran/mockery/man/stub.html) 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.
```{r s3-mockery}
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.
```{r, mock-fun}
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 `...`.
```{r local-mock}
tmp <- new.env()
mk <- local_mock(`curl::curl` = "mocked request", local_env = tmp)
dl(url)
mock_arg(mk, "url")
```