Compare commits

...

14 Commits

Author SHA1 Message Date
Alan Dipert
41694b3666 testServer(): Properly capture module return values 2020-04-29 22:13:00 +00:00
Winston Chang
25314f370e Merge pull request #2852 from rstudio/remove_test 2020-04-28 11:14:06 -05:00
Barret Schloerke
d6adffa273 testServer does not return results. Do not test for it. 2020-04-28 10:58:44 -04:00
Winston Chang
8ffc5aa20c Merge pull request #2849 from daattali/patch-1 2020-04-27 13:50:31 -05:00
Winston Chang
89c2f09864 Clearer wording for dryrun option 2020-04-27 13:21:02 -05:00
Dean Attali
ee3115653c typo in NEWS 2020-04-25 01:15:25 -04:00
Winston Chang
48115fc150 Merge pull request #2842 from rstudio/missing_monitorHandle 2020-04-24 15:34:01 -05:00
Winston Chang
d804a363ae Merge pull request #2837 from rstudio/testServer_args 2020-04-24 15:33:49 -05:00
Barret Schloerke
867c084990 check if function, not if not null 2020-04-24 16:30:21 -04:00
Barret Schloerke
8ffbfca97b do not call monitorHandle unless it is set 2020-04-24 15:51:23 -04:00
Barret Schloerke
ca9a72d25c testServer should return invisible() 2020-04-24 10:06:35 -04:00
Barret Schloerke
acdbe8ef5e use list instead of rlang::list2 2020-04-23 17:47:52 -04:00
Barret Schloerke
0f580ff23d remove '../' from loadSupport calls as they will be found automatically now 2020-04-23 14:55:21 -04:00
Barret Schloerke
e50981ccc0 replace ... with args in testServer 2020-04-23 14:19:03 -04:00
16 changed files with 99 additions and 76 deletions

View File

@@ -7,7 +7,7 @@ shiny 1.4.0.9001
### New features
* The new `shinyAppTemplate()` function creates a new template Shiny application, where components are optional, such as helper files in an R/ subdirectory, a module, and various kids of tests. ([#2704](https://github.com/rstudio/shiny/pull/2704))
* The new `shinyAppTemplate()` function creates a new template Shiny application, where components are optional, such as helper files in an R/ subdirectory, a module, and various kinds of tests. ([#2704](https://github.com/rstudio/shiny/pull/2704))
* `runTests()` is a new function that behaves much like R CMD check. `runTests()` invokes all of the top-level R files in the tests/ directory inside an application, in that application's environment. ([#2585](https://github.com/rstudio/shiny/pull/2585))

18
R/app.R
View File

@@ -238,8 +238,13 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
}
onStop <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
# This will cause `onStop` to be called.
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
if (is.function(monitorHandle)) {
monitorHandle()
monitorHandle <<- NULL
}
}
structure(
@@ -447,8 +452,13 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
}
onStop <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
# This will cause `onStop` to be called.
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
if (is.function(monitorHandle)) {
monitorHandle()
monitorHandle <<- NULL
}
}
structure(

View File

@@ -55,8 +55,8 @@
#' prompt the user to select which template items will be added to the new app
#' directory. With "all", all template items will be added to the app
#' directory.
#' @param dryrun If `TRUE`, don't actually write any files; just print out what
#' would be written.
#' @param dryrun If `TRUE`, don't actually write any files; just print out which
#' files would be written.
#'
#' @export
shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)

View File

@@ -134,10 +134,12 @@ moduleServer <- function(id, module, session = getDefaultReactiveDomain()) {
if (inherits(session, "MockShinySession")) {
body(module) <- rlang::expr({
session$setEnv(base::environment())
session$setReturned({ !!!body(module) })
!!body(module)
})
session$setReturned(callModule(module, id, session = session))
} else {
callModule(module, id, session = session)
}
callModule(module, id, session = session)
}

View File

@@ -18,10 +18,9 @@ isModuleServer <- function(x) {
#' in the server function environment, meaning that the parameters of the
#' server function (e.g. `input`, `output`, and `session`) will be available
#' along with any other values created inside of the server function.
#' @param ... Additional arguments to pass to the module function. These
#' arguments are processed with [rlang::list2()] and so are
#' _[dynamic][rlang::dyn-dots]_. If `app` is a module, and no `id` argument is
#' provided, one will be generated and supplied automatically.
#' @param args Additional arguments to pass to the module function.
#' If `app` is a module, and no `id` argument is provided, one will be
#' generated and supplied automatically.
#' @return The result of evaluating `expr`.
#' @include mock-session.R
#' @rdname testServer
@@ -37,7 +36,7 @@ isModuleServer <- function(x) {
#' })
#' }
#'
#' testServer(server, {
#' testServer(server, args = list(multiplier = 2), {
#' session$setInputs(x = 1)
#' # You're also free to use third-party
#' # testing packages like testthat:
@@ -49,14 +48,13 @@ isModuleServer <- function(x) {
#' stopifnot(myreactive() == 4)
#' stopifnot(output$txt == "I am 4")
#' # Any additional arguments, below, are passed along to the module.
#' }, multiplier = 2)
#' })
#' @export
testServer <- function(app = NULL, expr, ...) {
testServer <- function(app = NULL, expr, args = list()) {
require(shiny)
quosure <- rlang::enquo(expr)
args <- rlang::list2(...)
session <- getDefaultReactiveDomain()
if (inherits(session, "MockShinySession"))
@@ -142,4 +140,6 @@ testServer <- function(app = NULL, expr, ...) {
)
)
}
invisible()
}

View File

@@ -3,7 +3,7 @@ library(testthat)
test_dir(
"./testthat",
# Run in the app's environment containing all support methods.
env = shiny::loadSupport("../"),
env = shiny::loadSupport(),
# Display the regular progress output and throw an error if any test error is found
reporter = c("progress", "fail")
)

View File

@@ -17,8 +17,8 @@ prompt the user to select which template items will be added to the new app
directory. With "all", all template items will be added to the app
directory.}
\item{dryrun}{If \code{TRUE}, don't actually write any files; just print out what
would be written.}
\item{dryrun}{If \code{TRUE}, don't actually write any files; just print out which
files would be written.}
}
\description{
This function populates a directory with files for a Shiny application.

View File

@@ -4,7 +4,7 @@
\alias{testServer}
\title{Reactive testing for Shiny server functions and modules}
\usage{
testServer(app = NULL, expr, ...)
testServer(app = NULL, expr, args = list())
}
\arguments{
\item{app}{The path to an application or module to test. In addition to
@@ -19,10 +19,9 @@ in the server function environment, meaning that the parameters of the
server function (e.g. \code{input}, \code{output}, and \code{session}) will be available
along with any other values created inside of the server function.}
\item{...}{Additional arguments to pass to the module function. These
arguments are processed with \code{\link[rlang:list2]{rlang::list2()}} and so are
\emph{\link[rlang:dyn-dots]{dynamic}}. If \code{app} is a module, and no \code{id} argument is
provided, one will be generated and supplied automatically.}
\item{args}{Additional arguments to pass to the module function.
If \code{app} is a module, and no \code{id} argument is provided, one will be
generated and supplied automatically.}
}
\value{
The result of evaluating \code{expr}.
@@ -44,7 +43,7 @@ server <- function(id, multiplier = 2, prefix = "I am ") {
})
}
testServer(server, {
testServer(server, args = list(multiplier = 2), {
session$setInputs(x = 1)
# You're also free to use third-party
# testing packages like testthat:
@@ -56,5 +55,5 @@ testServer(server, {
stopifnot(myreactive() == 4)
stopifnot(output$txt == "I am 4")
# Any additional arguments, below, are passed along to the module.
}, multiplier = 2)
})
}

View File

@@ -2,7 +2,7 @@
withr::with_environment(
shiny::loadSupport("../"),
shiny::loadSupport(),
{
runner2_B <- 2

View File

@@ -2,6 +2,6 @@ library(testthat)
test_dir(
"./testthat",
env = shiny::loadSupport("../"),
env = shiny::loadSupport(),
reporter = c("progress", "fail")
)

View File

@@ -3,9 +3,11 @@ context("linkedScatterServer")
testServer(
linkedScatterServer,
data = reactive(ggplot2::mpg),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy")),
args = list(
data = reactive(ggplot2::mpg),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
),
{
# Init count... 0

View File

@@ -5,6 +5,6 @@ library(testthat)
# the supporting files that were already loaded into that env.
testthat::test_dir(
"./testthat",
env = shiny::loadSupport("../"),
env = shiny::loadSupport(),
reporter = c("summary", "fail")
)

View File

@@ -104,5 +104,5 @@ test_that("a Shiny app object with a module inside can be tested", {
})
test_that("It's an error to pass arguments to a server", {
expect_error(testServer(test_path("..", "test-modules", "06_tabsets"), {}, an_arg = 123))
expect_error(testServer(test_path("..", "test-modules", "06_tabsets"), {}, args = list(an_arg = 123)))
})

View File

@@ -17,9 +17,9 @@ test_that("Nested modules", {
})
}
testServer(parent, {
testServer(parent, args = list(id = "parent-id"), {
expect_equal(output$txt, "foo")
}, id = "parent-id")
})
})
@@ -30,9 +30,9 @@ test_that("Lack of ID", {
})
}
testServer(module, {
testServer(module, args = list(id = "foo"), {
expect_equal(output$txt, "foo-x")
}, id = "foo")
})
})
test_that("testServer works with nested module servers", {
@@ -50,10 +50,10 @@ test_that("testServer works with nested module servers", {
})
}
testServer(outerModule, {
testServer(outerModule, args = list(id = "foo"), {
session$setInputs(x = 1)
expect_equal(output$someVar, "a value: 2")
}, id = "foo")
})
})
test_that("testServer calls do not nest in module functions", {

View File

@@ -14,12 +14,12 @@ test_that("Variables outside of the module are inaccessible", {
}
}, envir = new.env(parent = globalenv()))
testServer(module, {
testServer(module, args = list(x = 0), {
expect_equal(x, 0)
expect_equal(y, 1)
expect_equal(z, 2)
expect_equal(exists("outside"), FALSE)
}, x = 0)
})
})
test_that("Variables outside the testServer() have correct visibility", {
@@ -34,11 +34,11 @@ test_that("Variables outside the testServer() have correct visibility", {
x <- 99
z <- 123
testServer(module, {
testServer(module, args = list(x = 0), {
expect_equal(x, 0)
expect_equal(y, 1)
expect_equal(z, 123)
}, x = 0)
})
})
test_that("testServer allows lexical environment access through session$env", {

View File

@@ -11,24 +11,7 @@ test_that("testServer passes dots", {
expect_equal(someArg, 123)
})
}
testServer(module, {}, someArg = 123)
})
test_that("testServer passes dynamic dots", {
module <- function(id, someArg) {
expect_false(missing(someArg))
moduleServer(id, function(input, output, session) {
expect_equal(someArg, 123)
})
}
# Test with !!! to splice in a whole named list constructed with base::list()
moreArgs <- list(someArg = 123)
testServer(module, {}, !!!moreArgs)
# Test with !!/:= to splice in an argument name
argName <- "someArg"
testServer(module, {}, !!argName := 123)
testServer(module, {}, args = list(someArg = 123))
})
test_that("testServer handles observers", {
@@ -414,7 +397,7 @@ test_that("testServer handles modules with additional arguments", {
testServer(module, {
expect_equal(output$txt1, "val1")
expect_equal(output$txt2, "val2")
}, arg1="val1", arg2="val2")
}, list(arg1="val1", arg2="val2"))
})
test_that("testServer captures htmlwidgets", {
@@ -565,25 +548,13 @@ test_that("accessing a non-existent output gives an informative message", {
testServer(module, {
expect_error(output$dontexist, "hasn't been defined yet: output\\$server1-dontexist")
}, id = "server1")
}, list(id = "server1"))
testServer(module, {
expect_error(output$dontexist, "hasn't been defined yet: output\\$.*-dontexist")
})
})
test_that("testServer returns a meaningful result", {
result <- testServer(function(id) {
moduleServer(id, function(input, output, session) {
reactive({ input$x * 2 })
})
}, {
session$setInputs(x = 2)
session$getReturned()()
})
expect_equal(result, 4)
})
test_that("assigning an output in a module function with a non-function errors", {
module <- function(id) {
moduleServer(id, function(input, output, session) {
@@ -680,3 +651,42 @@ test_that("session flush handlers work", {
})
})
test_that("module return value captured", {
module_implicit_return <- function(id) {
moduleServer(id, function(input, output, session) {
123
})
}
testServer(module_implicit_return, {
expect_equal(session$returned, 123)
})
module_early_returns <- function(id, n) {
retval <<- NULL
moduleServer(id, function(input, output, session) {
if (n == 0) return(n)
if (n %% 2 == 0) {
retval <<- "even"
} else {
return(FALSE)
}
retval
})
}
testServer(module_early_returns, {
expect_equal(session$returned, 0)
}, args = list(n = 0))
testServer(module_early_returns, {
expect_equal(session$returned, FALSE)
}, args = list(n = 1))
testServer(module_early_returns, {
expect_equal(session$returned, "even")
}, args = list(n = 2))
})
#test_that("server return value captured", {})