Environmental problems using testthat

310 Views Asked by At

I have some delicate issues with environments that are currently manifesting themselves in my unit tests. My basic structure is this

  • I have a main function main that has many arguments
  • wrapper is a wrapper function (one of many) that pertains only to selected arguments of main
  • helper is an intermediate helper function that is used by all wrapper functions

I use eval and match.call() to move between wrappers and the main function smoothly. My issue now is that my tests work when I run them line by line, but not using test_that().

Here is a MWE that shows the problem. If you step through the lines in the test manually, the test passes. However, evaluating the whole test_that() chunk the test fails because one of the arguments can not be found.

library(testthat)

wrapper <- function(a, b) {
  fun_call <- as.list(match.call())
  ret <- helper(fun_call)
  return(ret)
}

helper <- function(fun_call) {
  fun_call[[1]] <- quote(main)
  fun_call <- as.call(fun_call)
  fun_eval <- eval(as.call(fun_call))
  return(fun_eval)
}

main <- function(a, b, c = 1) {
  ret <- list(a = a, b = b, c = c)
  return(ret)
}

test_that("Test", {
  a <- 1
  b <- 2
  x <- wrapper(a = a, b = b)
  y <- list(a = 1, b = 2, c = 1)
  expect_equal(x, y)
})

With quite some confidence, I suspect I need to modify the default environment used by eval (i.e. parent.frame()), but I am not sure how to do this.

1

There are 1 best solutions below

7
On BEST ANSWER

You want to evaluate your call in your parent environment, not the local function environment. Change your helper to

helper <- function(fun_call) {
  fun_call[[1]] <- quote(main)
  fun_call <- as.call(fun_call)
  fun_eval <- eval.parent(fun_call, n=2)
  return(fun_eval)
}

This is assuming that helper is always called within wrapper which is called from somewhere else the parameters are defined.

It's not clear in this case that you really need all this non-standard evaulation. You might also consider a solution like

wrapper <- function(a, b) {
  helper(mget(ls()))
}
helper <- function(params) {
  do.call("main", params)
}

Here wrapper just bundles all it's parameters values into a list. Then you can just pass a list of parameters to helper and do.call will pass that list as parameters to your main function. This will evaluate the parameters of wrapper when you call it do you don't have to worry about the execution evironment.