mockery::mock() can't easily mock functions which defuse their arguments
FlorianMyronStork opened this issue · comments
Hello guys,
I've encounteres the issue, that a mock-object, created by mockery::mock()
can't handle functions which defuse their arguments:
my_function <- function(...) {
dots <- rlang::enexprs(...)
print(dots)
}
my_function(some_none_object)
# [[1]]
# some_none_object
my_wrapper <- function(...) {
my_function(...)
}
my_wrapper(some_none_object)
# [[1]]
# some_none_object
If I now test my function without any arguments, the test passes without failure.
describe("my_wrapper", {
it("calls my_function", {
mock_my_function <- mockery::mock("it does!")
mockery::stub(my_wrapper, "my_function", mock_my_function)
expect_equal(my_wrapper(), "it does!")
mockery::expect_called(mock_my_function, 1)
})
})
# No issues here, as no arguments were passed via ...
But, I test it with an argument given, I get this error.
describe("my_wrapper", {
it("calls my_function", {
mock_my_function <- mockery::mock("it does!")
mockery::stub(my_wrapper, "my_function", mock_my_function)
expect_equal(my_wrapper(some_none_object), "it does!")
mockery::expect_called(mock_my_function, 1)
})
})
# -- Error (Line 6): my_wrapper: calls my_function -------------------------------
# Error in `my_function(...)`: object 'some_none_object' not found
# Backtrace:
# 1. testthat::expect_equal(my_wrapper(some_none_object), "it does!")
# 4. drkleinrkprovisionentool::my_wrapper(some_none_object)
# 5. mockery my_function(...)
# at drkleinrkprovisionentool/R/test_mockery.R:11:2
The reason for this error might be found in mockery::mock
function (..., cycle = FALSE, envir = parent.frame())
{
stopifnot(is.environment(envir))
return_values <- eval(substitute(alist(...))) # <- right here. It does not defuse arguments.
return_values_env <- envir
call_no <- 0
calls <- list()
[...]
My workaround / solution to this is atm something like the following. I must admit, that I am not 100% familiar with the whole rlang:expr, rlang::enexpr, rlang::exprs, etc...
family of functions yet, but it does work:
describe("my_wrapper", {
it("calls my_function", {
mock_my_function <- mockery::mock("it does!")
mockery::stub(my_wrapper, "my_function", function(...) {
dots <- rlang::exprs(...)
mock_my_function(rlang::expr((!!!dots)))
})
expect_equal(my_wrapper(some_none_object), "it does!")
mockery::expect_called(mock_my_function, 1)
mockery::expect_args(mock_my_function, 1, quote((some_none_object)))
})
})
# Test passed
Even though, my expectations must be adopted a little bit to the workarround...
Might there be a solution to this?
Maybe in the form of a 2nd mock() funtcion, which uses defusing operators instead of substitute
? Or an optional argument like:
use_tidy_semantics = TRUE ?
That would be great! :)
We now recommend using testthat::local_mocked_binding()
instead, which doesn't have this problem.