Packages in R can have different types of dependencies on other packages. Some of these types indicate hard requirements, i.e. Depends
, Imports
and LinkingTo
.
However, there is a second category that indicate a softer dependency, i.e. Suggests
and Enhances
. In both these cases, the package provides additional functionality if the suggested / enhanced package is available.
Here is a concrete example: The package checkpoint
imports knitr
because knitr
helps checkpoint
to parse rmarkdown
files.
But now I am considering changing knitr
to a Suggests
dependency, i.e. only provide this functionality if knitr
is actually installed.
For proper unit testing, this means I have to test both scenarios:
knitr
is available, then do stuff.knitr
is not available, then throw a warning and do nothing.The actual R code is simple:
if(require(knitr)) {
do_stuff()
} else {
message("blah")
}
Question
But how can I set up unit tests for both scenarios?
The way I see it, the simple fact of checking for require(knitr)
will load the knitr
package if it is available in the local library.
So, to test for case 1, I have to install knitr
locally, meaning I can't test for case 2.
Is there a way of configuring testthat
(or any other unit testing framework) for this use case?
To test the branch followed when use require(knitr)
fails, use trace()
to temporarily modify require()
so that it won't find knitr, even if it is present on .libPaths()
. Specifically, in the body of require()
, reset the value of lib.loc=
to point to R.home()
-- an existing directory that does not contain a knitr
package.
This seems to work just as well in a package as it will in an interactive session in which you run the following:
find.package("knitr")
trace("require", quote(lib.loc <- R.home()), at=1)
isTRUE(suppressMessages(suppressWarnings(require(knitr))))
untrace("require")
isTRUE(suppressMessages(suppressWarnings(require(knitr))))
As I understand this, you have a function with two branches, one to be performed in R sessions for which require(knitr)
succeeds, and the other to be performed in sessions where it fails. You are then wanting to test this function "both ways" from a single R instance in which knitr actually is on .libPaths()
.
So basically you are needing some way to temporarily blind the call require(knitr)
to the actual presence of knitr. Completely and temporarily resetting the value returned by .libPaths()
looked promising, but doesn't seem to be possible.
Another promising avenue is to somehow reset the default value of lib.loc
in calls to require()
from NULL
(which means "use the value of .libPaths()
) to some other location where knitr is not available. You can't accomplish this by overwriting base::require()
, nor (in a package) can you get there by defining a local masking version of require()
with the desired value of lib.loc
.
It does, though, look like you can get away with using trace()
to temporarily modify require()
(blinding it to knitr's availability by setting lib.loc=R.home()
). Then do untrace()
to restore require()
to the vanilla version which will go ahead and find knitr.
Here's what that looked like in the dummy package I tested this with. First an R function that allows us to test for success along the two branches
## $PKG_SRC/R/hello.R
hello <- function(x=1) {
if(require(knitr)) {
x==2
} else {
x==3
}
}
Then a couple of tests, one for each branch:
## $PKG_SRC/inst/tests/testme.R
## Test the second branch, run when require(knitr) fails
trace("require", quote(lib.loc <- R.home()), at=1)
stopifnot(hello(3))
untrace("require")
## Test the first branch, run when require(knitr) succeeds
stopifnot(hello(2))
To test this, I used pkgKitten::kitten("dummy")
to set up a source directory, copied in these two files, added Suggests: knitr
to the DESCRIPTION
file, and then ran devtools::install()
and devtools::check()
from the appropriate directory. The package installs just fine, and passes all of the checks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With