rtestthat

how to test behavior that depends on a package being installed or not


I have a function like this:

func <- function(x) {
  if (requireNamespace("broom", quietly = TRUE)) {

    print(1)

  } else {

    print(2)

  }

I'd like to write tests using testthat that trigger both cases. But of course broom either is or isn't installed on my computer. What to do?


Solution

  • Edit: this no longer works as of testthat-2.0.0. According to a change in Oct 2017:

    • "Can't mock functions in base packages": You can no longer use with_mock() to mock functions in base packages, because this no longer works in R-devel due to changes with the byte code compiler. I recommend using mockery or mockr instead.

    Rest of the answer only applies to older versions of testthat.


    testthat::with_mock should do what you want.

    library(testthat)
    somefunc <- function() if (requireNamespace("base", quietly=TRUE)) 1L else 2L
    

    Some simple tests:

    expect_equal( somefunc(), 1L )
    

    Successful.

    expect_equal( somefunc(), 2L )
    # Error: somefunc() not equal to 2.
    # 1/1 mismatches
    # [1] 1 - 2 == -1
    

    Expected.

    Let's create a "mock" function that overrides the base function:

    with_mock(
      `base::requireNamespace` = function(package, ..., quietly=FALSE) FALSE,
      expect_equal( somefunc(), 1L )
    )
    # Error: somefunc() not equal to 1.
    # 1/1 mismatches
    # [1] 2 - 1 == 1
    
    with_mock(
      `base::requireNamespace` = function(package, ..., quietly=FALSE) FALSE,
      expect_equal( somefunc(), 2L )
    )
    # [1] 2
    

    Note: on success, expect_equal invisibly returns the return value, so you don't see [1] 1 in the first example. with_mock, on success, returns the return value but not invisibly. In both cases, failure will return a modified return value. This minute difference should not affect any tests.

    Depending on the function you are overriding, it makes sense (to me) to be careful to define the mock function with identical formals. You might be able to shortcut this if you know exactly how it is always called in all subordinate functions during your tests, but I think the extra careful attention to the formals will preclude really hard-to-troubleshoot test failures.

    NB: the help states that this

    ... is still experimental, so use with care.