<- function(package) {
check_installed if (is_installed(package)) {
return(invisible())
else {
} stop("Please install '{package}' before continuing")
} }
Appendix F — Tests (mocking)
Test code may rely on external systems, behavior, functions, or objects. To ensure that our unit tests remain fast and focused solely on the functional requirement being tested, it’s important to minimize these external dependencies.
The mocking functions can be used to substitute functions by emulating their behavior within the test scope (in BDD terms, mocks are creating the Given
conditions).1
Launch app with the shinypak
package:
launch('A.E-mocks-snapshots')
F.0.1 Example: mocking add-on functions
We’ll work through a unit test mocking example from the package development masterclass workshop at posit::conf(2023)
. Instead of real-time computations, mocks return predefined responses to given inputs. Consider the check_installed()
function below:
We’ll use local_mocked_bindings()
from testthat
to mock the behavior of rlang::is_installed()
. Below is a feature description for check_installed()
and two scenarios for each expected behavior:
Feature: Checking if an R package is installed
Scenario: Checking an installed package
Given the R package 'base' is installed
When I call the `check_installed()` function with 'base'
Then the function should return without any error
Scenario: Checking an uninstalled package
Given the R package 'foo' is not installed
When I call the `check_installed()` function with 'foo'
Then the function should raise an error with the message `Please install 'nonexistent_package' before continuing`
The check_installed()
shouldn’t be confused with rlang::check_installed()
, which checks if a package is installed, and if it isn’t, prompts the user install the package using pak::pkg_install()
.
Lets review how is_installed()
behaves with installed and missing packages:
::is_installed('foo')
rlang## [1] FALSE
::is_installed('base')
rlang## [1] TRUE
The version of check_installed()
in sap
will check if a package is installed and return invisible()
if it is (which, when assigned to an object, evaluates to NULL
):
check_installed('base')
<- check_installed('base')
x
x## NULL
If the package is not installed, check_installed()
prints an error message:
check_installed('foo')
## Error in check_installed("foo"): Please install '{package}' before continuing
To use mocking with is_installed()
, we’ll use the following syntax:
local_mocked_bindings(
{local function} = function(...) {value} )
In this case, {local function}
is is_installed()
from rlang
, and we want to test the two possible {value}
s (TRUE
/FALSE
).
In the first test, we’ll use expect_error()
to confirm that the error message is returned for an uninstalled package by using local_mocked_bindings()
and setting the is_installed()
value to FALSE
:
describe("Feature: Checking if an R package is installed", {
test_that(
"Scenario: Checking an uninstalled package
Given the R package 'foo' is not installed
When I call the `check_installed()` function with 'foo'
Then the function should raise an error with the message
`Please install 'nonexistent_package' before continuing`", {
test_logger(start = "mock is_installed", msg = "FALSE")
local_mocked_bindings(is_installed = function(package) FALSE)
expect_error(object = check_installed("foo"))
test_logger(end = "mock is_installed", msg = "FALSE")
})
})
- 1
-
Log test
start
andend
- 2
-
Set
{value}
toFALSE
- 3
- Pass a package we know is not installed
To test installed packages, we’ll confirm check_installed('foo')
with expect_invisible()
:
describe("Feature: Checking if an R package is installed", {
test_that(
"Scenario: Checking an installed package
Given the R package 'base' is installed
When I call the `check_installed()` function with 'base'
Then the function should return without any error", {
test_logger(start = "mock is_installed", msg = "TRUE")
local_mocked_bindings(is_installed = function(package) TRUE)
expect_invisible(check_installed("base"))
test_logger(end = "mock is_installed", msg = "TRUE")
}) })
- 1
-
Log test
start
andend
- 2
-
Set
{value}
toTRUE
- 3
- Pass a package we know is installed
The output from the tests above is provided below:
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 0 ]
INFO [2023-10-08 22:59:43] [ START mock is_installed = FALSE]
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ]
INFO [2023-10-08 22:59:43] [ END mock is_installed = FALSE]
INFO [2023-10-08 22:59:43] [ START mock is_installed = TRUE]
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ] INFO [2023-10-08 22:59:43] [ END mock is_installed = TRUE]
F.0.2 Notes on mocking
The roxygen2
documentation for check_installed()
uses the @importFrom
tag to import is_installed
and add it to the sap
namespace (using explicit namespacing alone won’t work):
#' Check if package is installed
#'
#' @description
#' An example function for demonstrating how to use `testthat`'s
#' mocking functions.
#'
#' @param package string, name of package
#'
#' @return invisible
#'
#' @importFrom rlang is_installed
#'
#' @export
#'
#' @examples
#' check_installed("foo")
#' check_installed("base")
- 1
-
Fortunately we already included rlang in our
DESCRIPTION
file for.data
inscatter_plot()