Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 949
R Yoda Avatar asked May 20 '18 19:05

R Yoda


People also ask

How do you mock a free function?

It's possible to use Google Mock to mock a free function (i.e. a C-style function or a static method). You just need to rewrite your code to use an interface (abstract class). public: ... virtual bool Open(const char* path, const char* mode) { return OpenFile(path, mode); } };

What is mocking a function?

Mock functions allow you to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new , and allowing test-time configuration of return values.

What is CallBase in MOQ?

CallBase , when initialized during a mock construction, is used to specify whether the base class virtual implementation will be invoked for mocked dependencies if no setup is matched. The default value is false . This is useful when mocking HTML/web controls of the System.


2 Answers

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)
          )
)
like image 93
Suren Avatar answered Oct 15 '22 12:10

Suren


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

like image 37
R Yoda Avatar answered Oct 15 '22 11:10

R Yoda