Writing client-side tests are more complicated than writing server-side tests. Whilst server-side tests call local functions, client-side tests check that the client-side function successfully calls the server-side function and receives the expected results. In this tutorial, we will use DSLite to run the client-side tests. The advantage of this approach is that it is easier to setup and run within the testthat
flow. The disadvantage is that the behaviour of DSLite may not mirror exactly the behaviour of a real-life scenario. You can view an alternative setup using real servers here.
The following is written as a separate script named tests/testthat/setupDSLite.R
.
library(DSLite)
library(devtools)
setupDSLite <- function() {
options(datashield.env = environment())
dslite.server <- DSLite::newDSLiteServer()
load_all("~/Library/Mobile Documents/com~apple~CloudDocs/work/dsExample")
dslite.server$config(defaultDSConfiguration(include=c("dsBase", "dsExample")))
dslite.server$aggregateMethod("funLevelsDS", "funLevelsDS")
dslite.server$aggregateMethod("listDisclosureSettingsDS", "listDisclosureSettingsDS")
builder <- DSI::newDSLoginBuilder()
builder$append(
server = "server_1",
url = "dslite.server",
driver = "DSLiteDriver"
)
logindata <- builder$build()
conns <- DSI::datashield.login(logins = logindata, assign = FALSE)
return(conns)
}
setupDSLite <- function() {
Define a function which can be called in your test scripts.
options(datashield.env = environment())
Set the DataSHIELD environment to be the global environment. It is necessary for DSLite to work with devtools::check()
and devtools::test()
.
load_all("~/Library/Mobile Documents/com~apple~CloudDocs/work/dsExample")
Load the server-side package so it can be used by DSLite.
dslite.server$config(defaultDSConfiguration(include=c("dsBase", "dsExample")))
dslite.server$aggregateMethod("funLevelsDS", "funLevelsDS")
dslite.server$aggregateMethod("listDisclosureSettingsDS", "listDisclosureSettingsDS")
Specify the server-side functions that can be used.
builder <- DSI::newDSLoginBuilder()
builder$append(
server = "server_1",
url = "dslite.server",
driver = "DSLiteDriver"
)
logindata <- builder$build()
conns <- DSI::datashield.login(logins = logindata, assign = FALSE)
return(conns)
Login to DSLite server.
library(testthat)
conns <- setupDSLite()
test_that("Valid inputs do not throw an error", {
expect_silent(.check_args("example", "This is a message"))
expect_silent(.check_args(c("a", "b"), "Message"))
})
test_that("NULL x throws an error", {
expect_error(.check_args(NULL, "This is a message"), "`x` must not be NULL")
})
test_that("NULL fun_message throws an error", {
expect_error(.check_args("example", NULL), "`fun_message` must not be NULL")
})
test_that("Non-character x throws an error", {
expect_error(.check_args(123, "This is a message"), "`x` must be a character vector")
expect_error(.check_args(TRUE, "This is a message"), "`x` must be a character vector")
expect_error(.check_args(list("a", "b"), "This is a message"), "`x` must be a character vector")
})
test_that("Non-character fun_message throws an error", {
expect_error(.check_args("example", 123), "`fun_message` must be a character vector")
expect_error(.check_args("example", FALSE), "`fun_message` must be a character vector")
expect_error(.check_args("example", list("a")), "`fun_message` must be a character vector")
})
test_that("ds.funLevels returns the correct message", {
expect_equal(
ds.funLevels(
x = "iris$Species",
fun_message = "ThisIsAFunMessage",
datasources = conns),
list(server_1 = "ThisIsAFunMessage: setosa, versicolor, virginica")
)
})
test_that("ds.funLevels returns an error if factor has too many levels", {
expect_error(
ds.funLevels(
x = "mtcars$disp",
fun_message = "ThisIsAFunMessage",
datasources = conns)
)
})
conns <- setupDSLite()
Call the function that we defined to set up the DSLite server.
test_that("Valid inputs do not throw an error", {
expect_silent(.check_args("example", "This is a message"))
expect_silent(.check_args(c("a", "b"), "Message"))
})
Test that if character vectors are provided to x
and fun_message
no error is thrown.
test_that("NULL x throws an error", {
expect_error(.check_args(NULL, "This is a message"), "`x` must not be NULL")
})
test_that("NULL fun_message throws an error", {
expect_error(.check_args("example", NULL), "`fun_message` must not be NULL")
})
Test that an error is thrown if either x
or fun_message
is null.
test_that("Non-character x throws an error", {
expect_error(.check_args(123, "This is a message"), "`x` must be a character vector")
expect_error(.check_args(TRUE, "This is a message"), "`x` must be a character vector")
expect_error(.check_args(list("a", "b"), "This is a message"), "`x` must be a character vector")
})
test_that("Non-character fun_message throws an error", {
expect_error(.check_args("example", 123), "`fun_message` must be a character vector")
expect_error(.check_args("example", FALSE), "`fun_message` must be a character vector")
expect_error(.check_args("example", list("a")), "`fun_message` must be a character vector")
})
Test that an error if thrown if either x
or fun_message
is not a character.
test_that("ds.funLevels returns the correct message", {
expect_equal(
ds.funLevels(
x = "iris$Species",
fun_message = "ThisIsAFunMessage",
datasources = conns),
list(server_1 = "ThisIsAFunMessage: setosa, versicolor, virginica")
)
})
Test that ds.funLevels returns the correct message in a valid use case.
test_that("ds.funLevels returns an error if factor has too many levels", {
expect_error(
ds.funLevels(
x = "mtcars$disp",
fun_message = "ThisIsAFunMessage",
datasources = conns)
)
})
Test that ds.funLevels returns an error if the number of levels in the factor is too high.