Using Mockups
In some cases we might be unable to test functionalities. This might be due to long time of computation, or the function needs inputs that are stored on an external server. This also includes functions that deal with data that change in time (for instance data provided by an external API). This problems can be solved by using mockups. Mockups are simply prefabricated data sets. There are two ways we can use them, depending on how the function processes them.
Let’s say we have a function that takes the output of a STAN model and makes a summary.
model_summary <- function(model) {
summary <- model %>%
... %>%
...
return(summary)
}
As you see, we directly pass the output of the modeling function as an argument and the function processes it. In this case, we can make a mockup by calculating the model and saving it as a .rds file. The important thing is that this needs to be saved in the tests/testthat folder. Having this we cloud write a test:
test_that("summary works", {
model_output <- readRDS("model_output.rds")
expected_results <- c(1,2,3)
expect_equal(model_summary(model_output), c(1, 2, 3))
})
The other case is best explained by an example. Let’s say one of the arguments of our function is a data set name. This data set is stored on some external server. At some point our function connects to external server, downloads the data set and processes it further.
filter_remote <- function(name, N) {
dict_raw <- aws.s3::s3readRDS(
object = name,
bucket = "S3bucket/data"
) %>%
filter(n > N)
}
Other problems of similar nature are:
- passing credentials in a function
- intensive computation
- receiving data from external API that changes over time
To avoid problems like: extensive computation, storing vulnerable data in tests, connecting to 3rd party sources, etc. we need to somehow substitute part of the code with a mockup (here we would need to substitute aws.s3::s3readRDS() with mocked up data). One fairly simple way to do this is to use mockery::stub() function. Assume that we already made a mocked up mocked_data.rds file that will be used in test. Now we can write:
test_that("filter_remote correctly filters rows", {
mockery::stub(
filter_remote,
"aws.s3::s3readRDS",
readRDS("mocked_data.rds")
)
N1 <- 3
N2 <- 0
filtering_results_1 <- filter_remote("mocked_data", N1)
filtering_results_2 <- filter_remote("mocked_data", N2)
expect_true(all(filtering_results_1$n > N1))
expect_true(all(filtering_results_2$n > N2))
})
Anytime you need to replace a part of a function within testing environment with other function (e.g. reading a mockup) or a value (for instance passing data frame directly), you can use this trick.