Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/nbenn/mockthat

Mocking for testthat unit testing
https://github.com/nbenn/mockthat

Last synced: 9 days ago
JSON representation

Mocking for testthat unit testing

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
out.width = "100%"
)
```

# mockthat

[![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)

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")
```