How to mock functions from base?
Asked Answered
D

2

12

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

Diley answered 20/5, 2018 at 19:17 Comment(0)
I
6

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)
          )
)
Interval answered 20/5, 2018 at 22:58 Comment(4)
Thanks a lot, it seems to work even within a package. Your answer is based on my defective example code were Sys.info returns a character vector while my mock function returns a list so you have fixed my problem + it works now even to mock base functions. Either the help for with_mock is outdated or something else has changed. I will not update my wrong code example for consistency, please update your answer to correct my mock with Sys.info = function() return(c(sysname = "Clever OS")).Diley
I have published a github repo to evaluate the problem within a package (mocking a base function within a package works! - even though the help for with_mock says it does not work): github.com/aryoda/testthat_base_mockingDiley
@RYoda Glad, it helped. Why don't you edit my answer? If not, I will do so later.Interval
@Surren Edit done, please verify and adjust as you wantDiley
D
3

Warning about mocking base functions using with_mock:

Even though mocking of base functions may work in case of my question and the accepted answer of @Suren many issues around with_mock in the package testthat disencourage to mock base package functions (or even functions outside of the package under test), e. g.

testthat 2.0.0 - Breaking API changes

  • "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.

Don't allow base packages to be mocked

with_mock() function seems to interact badly with the JIT compiler

  • As I mentioned in hadley/testthat#546 (comment), mockery already has a replacement function for with_mock called stub, which takes a different approach to mocking things out that is not restricted by the issues raised in the other threads. If with_mock breaks, I think this package would work just as well with mockery. I also think mockery would be a reasonable place to house with_mock, where it could live alongside stub for legacy reasons.

Prevent with_mock from touching base R packages

Diley answered 21/5, 2018 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.