rmockingtestthat

How to mock functions from base?


I am calling a function from base in my code and I want to mock this function in my testthat unit test.

How can I do this?

library(testthat)

my.func <- function() {
  return(Sys.info()["sysname"])   # e. g. "Linux"
}

my.func()
# sysname 
# "Linux" 

test_that("base function can be mocked",
  with_mock(
    Sys.info = function() return(list(sysname = "Clever OS")),  # see edit 2 !!!
    expect_equal(my.func(), "Clever OS", fixed = TRUE)
  )
)
# Error: Test failed: 'base function can be mocked'
# * my.func() not equal to "Clever OS".

?with_mock says:

Functions in base packages cannot be mocked, but this can be worked around easily by defining a wrapper function.

I could encapsulate the base function call to Sys.info() by a wrapper function that I call from my.func then but let's assume I cannot do this because I am testing a function from a package that I cannot change...

Any solution for this?

I am using R3.4.4 64 Bit on Ubuntu 14.04 with testthat 2.0.0.9000.

Edit 1:

Using

`base::Sys.info` = function() return(list(sysname = "Clever OS"))

results in the testthat error msg:

Can't mock functions in base packages (base)

Edit 2: As @suren show in his answer my code example here is wrong (the mocked function returns another class then the original one :-(

The correct mock function should be: Sys.info = function() return(c(sysname = "Clever OS"))


Solution

  • The error message is my.func() not equal to "Clever OS".. The reason is that Sys.info returns a named character vector while your mocking function a list.

    Just modify the mocking function and the expected value and it works:

    test_that("base function can be mocked",
      with_mock(
        Sys.info = function() return(c(sysname = "Clever OS")),
        expect_equal(my.func(), c(sysname = "Clever OS"), fixed = TRUE)
      )
    )
    

    This works even within a package.

    Note: Mocking of base functions shouldn't work according to the help of with_mock but it does (at the moment at least).

    The following (my.func()$`sysname`) seems to pass the test with the original code from the question.

    test_that("base function can be mocked",
              with_mock(
                Sys.info = function() return(list(sysname = "Clever OS")), 
                expect_equal(my.func()$`sysname`, "Clever OS", fixed = TRUE)
              )
    )
    

    Alternatively, have a list where the string in expect_equal

    test_that("base function can be mocked",
              with_mock(
                Sys.info = function() return(list(sysname = "Clever OS")),  
                expect_equal(my.func(), list(`sysname` = "Clever OS"), fixed = TRUE)
              )
    )