如何从基础模拟函数?

How to mock functions from base?

我在我的代码中从 base 调用一个函数,我想在我的 testthat 单元测试中模拟这个函数。

我该怎么做?

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 说:

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

我可以通过我从 my.func 调用的包装函数来封装对 Sys.info() 的基本函数调用,但假设我不能这样做,因为我正在测试一个包中的函数,我不能改变...

有什么解决办法吗?

我在 Ubuntu 14.04 上使用 R3.4.4 64 位和 testthat 2.0.0.9000。

编辑 1:

使用

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

导致 testthat 错误消息:

Can't mock functions in base packages (base)

编辑 2: 正如@suren 在他的回答中显示我的代码示例是错误的(模拟函数 returns 另一个 class 然后是原始函数:-(

正确的 mock 函数应该是:Sys.info = function() return(c(sysname = "Clever OS"))

错误信息是my.func()不等于"Clever OS".。 原因是 Sys.info returns 一个命名的字符向量,而你的模拟函数是 list.

只需修改模拟函数和期望值即可:

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

这甚至在一个包中也有效。

注意:根据 with_mock 的帮助,基函数的模拟不应该起作用,但它确实起作用(至少目前)。

以下 (my.func()$`sysname`) 似乎通过了问题中的原始代码的测试。

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

或者,有一个列表,其中 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)
          )
)

关于使用 with_mock 模拟基函数的警告:

即使在我的问题和@Suren 接受的答案的情况下,基函数的模拟可能会起作用,包 testthat 中围绕 with_mock 的许多问题不鼓励模拟基包函数(甚至在被测包之外的功能),e。 g.

testthat 2.0.0 - Breaking API changes

  • "Can't mock functions in base packages":您不能再使用 with_mock() 来模拟基础包中的函数,因为由于字节码编译器的变化,这在 R-devel 中不再有效。 我建议改用 mockerymockr

Don't allow base packages to be mocked

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

  • 正如我在 hadley/testthat#546(评论)中提到的,mockery 已经有一个替代函数 with_mock 称为存根,它采用不同的方法来模拟事物不受其他线程中提出的问题的限制。如果 with_mock 坏了,我认为这个包在 mockery 上也能正常工作。我还认为 mockery 是放置 with_mock 的合理位置,出于遗留原因,它可以与 stub 并存。

Prevent with_mock from touching base R packages