# Mocking with mockr

The mockr package helps testing code that relies on functions that are slow, have unintended side effects or access resources that may not be available when testing. It allows replacing such functions with deterministic mock functions. This article gives an overview and introduces a few techniques.

    library(mockr)

## General idea

Let’s assume a function `access_resource()` that accesses some resource. This works in normal circumstances, but not during tests. A function `work_with_resource()` works with that resource. How can we test `work_with_resource()` without adding too much logic to the implementation?

    access_resource <- function() {
      message("Trying to access resource...")
      # For some reason we can't access the resource in our tests.
      stop("Can't access resource now.")
    }

    work_with_resource <- function() {
      resource <- access_resource()
      message("Fetched resource: ", resource)
      invisible(resource)
    }

In our example, calling the worker function gives an error:

    work_with_resource()
    #> Trying to access resource...
    #> Error in access_resource(): Can't access resource now.

We can use `local_mock()` to temporarily replace the implementation of `access_resource()` with one that doesn’t throw an error:

    access_resource_for_test <- function() {
      # We return a value that's good enough for testing
      # and can be computed quickly:
      42
    }

    local({
      # Here, we override the function that raises the error
      local_mock(access_resource = access_resource_for_test)

      work_with_resource()
    })
    #> Fetched resource: 42

The use of `local()` here is required for technical reasons. This package is most useful in conjunction with testthat, the remainder of this article will focus on that use case.

## Create demo package

We create a package called {mocktest} for demonstration. For this demo, the package is created in a temporary directory. A real project will live somewhere in your home directory. The `usethis::create_package()` function sets up a package project ready for development. The output shows the details of the package created.

    pkg <- usethis::create_package(file.path(tempdir(), "mocktest"))
    #> ✔ Creating
    #>   '/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/Rtmpre1pOM/mocktest/'.
    #> ✔ Setting active project to
    #>   "/private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/Rtmpre1pOM/mocktest".
    #> ✔ Creating 'R/'.
    #> ✔ Writing 'DESCRIPTION'.
    #> Package: mocktest
    #> Title: What the Package Does (One Line, Title Case)
    #> Version: 0.0.0.9000
    #> Authors@R (parsed):
    #>     * First Last <first.last@example.com> [aut, cre]
    #> Description: What the package does (one paragraph).
    #> License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
    #>     license
    #> Encoding: UTF-8
    #> Roxygen: list(markdown = TRUE)
    #> RoxygenNote: 7.3.2.9000
    #> ✔ Writing 'NAMESPACE'.
    #> ✔ Setting active project to "<no active project>".

In an interactive RStudio session, a new window opens. Users of other environments would change the working directory manually. For this demo, we manually set the active project.

    usethis::proj_set()
    #> ✔ Setting active project to
    #>   "/private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/Rtmpre1pOM/mocktest".

The infrastructure files and directories that comprise a minimal R package are created:

    fs::dir_tree()
    #> .
    #> ├── DESCRIPTION
    #> ├── NAMESPACE
    #> └── R

## Import function

We copy the functions from the previous example (under different names) into the package. Normally we would use a text editor:

    cat > R/resource.R <<"EOF"
    access_resource_pkg <- function() {
      message("Trying to access resource...")
      # For some reason we can't access the resource in our tests.
      stop("Can't access resource now.")
    }

    work_with_resource_pkg <- function() {
      resource <- access_resource_pkg()
      message("Fetched resource: ", resource)
      invisible(resource)
    }
    EOF

Loading the package and calling the function gives the error we have seen before:

    pkgload::load_all()
    #> ℹ Loading mocktest
    work_with_resource_pkg()
    #> Trying to access resource...
    #> Error in access_resource_pkg(): Can't access resource now.

## Adding test with mock

We create a test that tests `work_with_resource_pkg()`, mocking `access_resource_pkg()`. We need to prefix with the package name, because testthat provides its own `testthat::local_mock()` which is now deprecated.

    usethis::use_testthat()
    #> ✔ Adding testthat to 'Suggests' field in DESCRIPTION.
    #> ✔ Adding "3" to 'Config/testthat/edition'.
    #> ✔ Creating 'tests/testthat/'.
    #> ✔ Writing 'tests/testthat.R'.
    #> ☐ Call `usethis::use_test()` to initialize a basic test file and open it for
    #>   editing.

    cat > tests/testthat/test-resource.R <<"EOF"
    test_that("Can work with resource", {
      mockr::local_mock(access_resource_pkg = function() {
        42
      })

      expect_message(
        expect_equal(work_with_resource_pkg(), 42)
      )
    })
    EOF

The test succeeds:

    testthat::test_local(reporter = "location")
    #>
    #> Attaching package: 'testthat'
    #> The following objects are masked from 'package:mockr':
    #>
    #>     local_mock, with_mock
    #> Start test: Can work with resource
    #>   'test-resource.R:6:3' [success]
    #>   'test-resource.R:6:3' [success]
    #> End test: Can work with resource

## Run individual tests

mockr is aware of testthat and will work even if executing the tests in the current session. This is especially handy if you want to troubleshoot single tests:

    test_that("Can work with resource", {
      mockr::local_mock(access_resource_pkg = function() {
        42
      })

      expect_message(
        expect_equal(work_with_resource_pkg(), 42)
      )
    })
    #> Test passed 🥳

## Write wrapper functions

mockr can only mock functions in the current package. To substitute implementations of functions in other packages, create wrappers in your package and use these wrappers exclusively.

The example below demonstrates a `d6()` function that is used to get the value of a random die throw. Instead of using `runif()` directly, this function uses `my_runif()` which wraps `runif()`.

    cat > R/runif.R <<"EOF"
    my_runif <- function(...) {
      runif(...)
    }

    d6 <- function() {
      trunc(my_runif(1, 0, 6)) + 1
    }
    EOF

    pkgload::load_all()
    #> ℹ Loading mocktest

This allows testing the behavior of `d6()`:

    test_that("d6() works correctly", {
      seq <- c(0.32, 5.4, 5, 2.99)
      my_runif_mock <- function(...) {
        on.exit(seq <<- seq[-1])
        seq[[1]]
      }

      mockr::local_mock(my_runif = my_runif_mock)

      expect_equal(d6(), 1)
      expect_equal(d6(), 6)
      expect_equal(d6(), 6)
      expect_equal(d6(), 3)
    })
    #> Test passed 😸

## Mock S3 methods

mockr cannot substitute implementations of S3 methods. To substitute methods for a class `"foo"`, implement a subclass and add new methods only for that subclass. The pillar package contains an example where a class with changed behavior for `dim()` and `head()` for the sole purpose of testing.