【问题标题】:How to mock functions from base?如何从基础模拟函数?
【发布时间】:2018-10-30 12:03:12
【问题描述】:

我在我的代码中从 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 说:

基础包中的函数不能被模拟,但这可以工作 通过定义一个包装函数来轻松解决。

我可以通过我从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 错误消息:

不能模拟基础包(base)中的函数

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

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

【问题讨论】:

    标签: r mocking testthat


    【解决方案1】:

    错误消息是my.func() 不等于“Clever OS”。。 原因是 Sys.info 返回一个命名字符向量,而您的模拟函数返回一个 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)
              )
    )
    

    【讨论】:

    • 非常感谢,它似乎甚至在一个包中也可以工作。您的答案基于我的有缺陷的示例代码,Sys.info 返回一个字符向量,而我的模拟函数返回一个列表,所以你已经解决了我的问题 + 它现在甚至可以模拟基本函数。 with_mock 的帮助已过时或其他内容已更改。我将不会更新我的错误代码示例以保持一致性,请更新您的答案以使用 Sys.info = function() return(c(sysname = "Clever OS")) 更正我的模拟。
    • 我已经发布了一个 github repo 来评估包中的问题(模拟包中的基本函数有效!-即使with_mock 的帮助说它不起作用):github.com/aryoda/testthat_base_mocking
    • @RYoda 很高兴,它有帮助。你为什么不编辑我的答案?如果没有,我稍后会这样做。
    • @Surren 编辑完成,请根据需要进行验证和调整
    【解决方案2】:

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

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

    testthat 2.0.0 - Breaking API changes

    • “无法模拟基础包中的函数”:您不能再使用 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 已经有一个名为 stub 的 with_mock 替换函数,它采用不同的方法来模拟不受问题限制的事物在其他线程中提出。如果 with_mock 中断,我认为这个包在嘲弄时也能正常工作。我还认为 mocky 是一个合理的地方,可以容纳 _mock,因为遗留原因它可以与 stub 一起存在。

    Prevent with_mock from touching base R packages

    【讨论】:

      猜你喜欢
      • 2013-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-13
      • 2021-06-30
      相关资源
      最近更新 更多