如何在testthat单元测试中Mock基础包中的函数?
这个问题确实挺棘手的——testthat默认不让mock基础包的函数,又不能改被测代码。不过有几个不用修改原函数的解决方案,适合你的场景:
方案1:临时向函数环境注入Mock实现
R的函数会优先从自身绑定的环境里查找调用的函数,我们可以把Mock版的Sys.info临时放到my.func的环境中,测试结束后再清理,完全不会影响原函数的正常逻辑:
library(testthat) my.func <- function() { return(Sys.info()["sysname"]) } test_that("base function can be mocked via environment injection", { # 把Mock的Sys.info注入到my.func的专属环境 environment(my.func)$Sys.info <- function() c(sysname = "Clever OS") # 测试完成后立即清理,避免干扰其他测试用例 on.exit(rm(Sys.info, envir = environment(my.func))) expect_equal(my.func(), "Clever OS", fixed = TRUE) })
方案2:用trace临时替换函数行为
利用R内置的trace函数,我们可以给Sys.info添加一个“退出钩子”,让它在执行时返回我们指定的值,测试结束后取消钩子即可:
test_that("base function can be mocked via trace", { # 给Sys.info添加退出钩子,强制返回Mock值 trace(Sys.info, exit = function() return(c(sysname = "Clever OS")), print = FALSE) # 必须在测试结束后取消trace,否则会影响全局环境的Sys.info on.exit(untrace(Sys.info)) expect_equal(my.func(), "Clever OS", fixed = TRUE) })
方案3:用mockery包的stub函数(推荐)
如果可以安装额外包,mockery的stub函数专门解决这类无法直接Mock的场景,它会直接替换目标函数内部对指定函数的调用逻辑,写法更简洁:
# 第一次使用先安装包 # install.packages("mockery") library(mockery) library(testthat) test_that("base function can be mocked via mockery::stub", { # 直接指定要替换的函数和Mock实现 stub(my.func, "Sys.info", function() c(sysname = "Clever OS")) expect_equal(my.func(), "Clever OS", fixed = TRUE) })
额外注意
别忘了你之前踩过的返回类型坑:Sys.info()本身返回的是命名向量,不是列表,所以Mock函数一定要返回c(sysname = "Clever OS"),否则预期值对比会失败。
内容的提问来源于stack exchange,提问作者R Yoda
相关产品推荐
相关产品推荐

