Search code examples
runit-testingglobal-variablesr-package

Unit testing functions with global variables in R


Preamble: package structure

I have an R package that contains an R/globals.R file with the following content (simplified):

utils::globalVariables("COUNTS")

Then I have a function that simply uses this variable. For example, R/addx.R contains a function that adds a number to COUNTS

addx <- function(x) {
    COUNTS + x
}

This is all fine when doing a devtools::check() on my package, there's no complaining about COUNTS being out of the scope of addx().

Problem: writing a unit test

However, say I also have a tests/testthtat/test-addx.R file with the following content:

test_that("addition works", expect_gte(fun(1), 1))

The content of the test doesn't really matter here, because when running devtools::test() I get an "object 'COUNTS' not found" error.

What am I missing? How can I correctly write this test (or setup my package).

What I've tried to solve the problem

  1. Adding utils::globalVariables("COUNTS") to R/addx.R, either before, inside or after the function definition.
  2. Adding utils::globalVariables("COUNTS") to tests/testthtat/test-addx.R in all places I could think of.
  3. Manually initializing COUNTS (e.g., with COUNTS <- 0 or <<- 0) in all places of tests/testthtat/test-addx.R I could think of.
  4. Reading some examples from other packages on GitHub that use a similar syntax (source).

Solution

  • I think you misunderstand what utils::globalVariables("COUNTS") does. It just declares that COUNTS is a global variable, so when the code analysis sees

    addx <- function(x) {
        COUNTS + x
    }
    

    it won't complain about the use of an undefined variable. However, it is up to you to actually create the variable, for example by an explicit

    COUNTS <- 0
    

    somewhere in your source. I think if you do that, you won't even need the utils::globalVariables("COUNTS") call, because the code analysis will see the global definition.

    Where you would need it is when you're doing some nonstandard evaluation, so that it's not obvious where a variable comes from. Then you declare it as a global, and the code analysis won't worry about it. For example, you might get a warning about

    subset(df, Col1 < 0)
    

    because it appears to use a global variable named Col1, but of course that's fine, because the subset() function evaluates in a non-standard way, letting you include column names without writing df$Col.