-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce integration testing framework #2682
Merged
+997
−2
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
263f8a8
Introduce integration testing functionality
trestletech 42f6adb
Handle Joe's feedback.
trestletech f47b151
Test improvements for Windows and make CHECK pass.
trestletech 5105ecb
Cleaning up the vignette
trestletech 34b4859
Merge remote-tracking branch 'origin/master' into jeff/int-test
trestletech 799c5ac
Clean up test warnings
trestletech 5a74e36
Implement missing test.
trestletech 0776f71
Export session
trestletech 0cad13b
Placeholder docs for MockShinySession
trestletech 0e34221
How do I still get paid to do this?
trestletech 959dc7f
PR feedback
trestletech 1f4a3c4
Regenerate docs
trestletech 5fbaa26
Remove vignette.
trestletech File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
|
||
|
||
#' Test a shiny module | ||
#' @param module The module to test | ||
#' @param expr Test code containing expectations. The test expression will run | ||
#' in the module's environment, meaning that the module's parameters (e.g. | ||
#' `input`, `output`, and `session`) will be available along with any other | ||
#' values created inside of the module. | ||
#' @param args A list of arguments to pass into the module beyond `input`, | ||
#' `output`, and `session`. | ||
#' @param ... Additional named arguments to be passed on to the module function. | ||
#' @include mock-session.R | ||
#' @export | ||
testModule <- function(module, expr, args, ...) { | ||
expr <- substitute(expr) | ||
.testModule(module, expr, args, ...) | ||
} | ||
|
||
#' @noRd | ||
#' @importFrom withr with_options | ||
.testModule <- function(module, expr, args, ...) { | ||
# Capture the environment from the module | ||
# Inserts `session$env <- environment()` at the top of the function | ||
fn_body <- body(module) | ||
fn_body[seq(3, length(fn_body)+1)] <- fn_body[seq(2, length(fn_body))] | ||
fn_body[[2]] <- quote(session$env <- environment()) | ||
body(module) <- fn_body | ||
|
||
# Create a mock session | ||
session <- MockShinySession$new() | ||
|
||
# Parse the additional arguments | ||
args <- list(..., input = session$input, output = session$output, session = session) | ||
|
||
# Initialize the module | ||
isolate( | ||
withReactiveDomain( | ||
session, | ||
withr::with_options(list(`shiny.allowoutputreads`=TRUE), { | ||
# Remember that invoking this module implicitly assigns to `session$env` | ||
# Also, assigning to `$returned` will cause a flush to happen automatically. | ||
session$returned <- do.call(module, args) | ||
}) | ||
) | ||
) | ||
|
||
# Run the test expression in a reactive context and in the module's environment. | ||
# We don't need to flush before entering the loop because the first expr that we execute is `{`. | ||
# So we'll already flush before we get to the good stuff. | ||
isolate({ | ||
withReactiveDomain( | ||
session, | ||
withr::with_options(list(`shiny.allowoutputreads`=TRUE), { | ||
eval(expr, new.env(parent=session$env)) | ||
}) | ||
) | ||
}) | ||
|
||
if (!session$isClosed()){ | ||
session$close() | ||
} | ||
} | ||
|
||
#' Test an app's server-side logic | ||
#' @param expr Test code containing expectations | ||
#' @param appDir The directory root of the Shiny application. If `NULL`, this function | ||
#' will work up the directory hierarchy --- starting with the current directory --- | ||
#' looking for a directory that contains an `app.R` or `server.R` file. | ||
#' @export | ||
testServer <- function(expr, appDir=NULL) { | ||
if (is.null(appDir)){ | ||
appDir <- findApp() | ||
} | ||
|
||
app <- shinyAppDir(appDir) | ||
server <- app$serverFuncSource() | ||
|
||
# Add `session` argument if not present | ||
fn_formals <- formals(server) | ||
if (! "session" %in% names(fn_formals)) { | ||
fn_formals$session <- bquote() | ||
formals(server) <- fn_formals | ||
} | ||
|
||
# Now test the server as we would a module | ||
.testModule(server, expr=substitute(expr)) | ||
} | ||
|
||
findApp <- function(startDir="."){ | ||
dir <- normalizePath(startDir) | ||
|
||
# The loop will either return or stop() itself. | ||
while (TRUE){ | ||
if(file.exists.ci(file.path(dir, "app.R")) || file.exists.ci(file.path(dir, "server.R"))){ | ||
return(dir) | ||
} | ||
|
||
# Move up a directory | ||
origDir <- dir | ||
dir <- dirname(dir) | ||
|
||
# Testing for "root" path can be tricky. OSs differ and on Windows, network shares | ||
# might have a \\ prefix. Easier to just see if we got stuck and abort. | ||
if (dir == origDir){ | ||
# We can go no further. | ||
stop("No shiny app was found in ", startDir, " or any of its parent directories") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
library(shiny) | ||
|
||
global <- 123 | ||
|
||
# Define UI for random distribution app ---- | ||
ui <- fluidPage( | ||
|
||
# App title ---- | ||
titlePanel("Tabsets"), | ||
|
||
# Sidebar layout with input and output definitions ---- | ||
sidebarLayout( | ||
|
||
# Sidebar panel for inputs ---- | ||
sidebarPanel( | ||
|
||
# Input: Select the random distribution type ---- | ||
radioButtons("dist", "Distribution type:", | ||
c("Normal" = "norm", | ||
"Uniform" = "unif", | ||
"Log-normal" = "lnorm", | ||
"Exponential" = "exp")), | ||
|
||
# br() element to introduce extra vertical spacing ---- | ||
br(), | ||
|
||
# Input: Slider for the number of observations to generate ---- | ||
sliderInput("n", | ||
"Number of observations:", | ||
value = 500, | ||
min = 1, | ||
max = 1000) | ||
|
||
), | ||
|
||
# Main panel for displaying outputs ---- | ||
mainPanel( | ||
|
||
# Output: Tabset w/ plot, summary, and table ---- | ||
tabsetPanel(type = "tabs", | ||
tabPanel("Plot", plotOutput("plot")), | ||
tabPanel("Summary", verbatimTextOutput("summary")), | ||
tabPanel("Table", tableOutput("table")) | ||
) | ||
|
||
) | ||
) | ||
) | ||
|
||
# Define server logic for random distribution app ---- | ||
server <- function(input, output) { | ||
|
||
# Reactive expression to generate the requested distribution ---- | ||
# This is called whenever the inputs change. The output functions | ||
# defined below then use the value computed from this expression | ||
d <- reactive({ | ||
dist <- switch(input$dist, | ||
norm = rnorm, | ||
unif = runif, | ||
lnorm = rlnorm, | ||
exp = rexp, | ||
rnorm) | ||
|
||
dist(input$n) | ||
}) | ||
|
||
# Generate a plot of the data ---- | ||
# Also uses the inputs to build the plot label. Note that the | ||
# dependencies on the inputs and the data reactive expression are | ||
# both tracked, and all expressions are called in the sequence | ||
# implied by the dependency graph. | ||
output$plot <- renderPlot({ | ||
dist <- input$dist | ||
n <- input$n | ||
|
||
hist(d(), | ||
main = paste("r", dist, "(", n, ")", sep = ""), | ||
col = "#75AADB", border = "white") | ||
}) | ||
|
||
# Generate a summary of the data ---- | ||
output$summary <- renderPrint({ | ||
summary(d()) | ||
}) | ||
|
||
# Generate an HTML table view of the data ---- | ||
output$table <- renderTable({ | ||
d() | ||
}) | ||
|
||
} | ||
|
||
# Create Shiny app ---- | ||
shinyApp(ui, server) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
library(shiny) | ||
|
||
# Define server logic for random distribution app ---- | ||
function(input, output) { | ||
|
||
# Reactive expression to generate the requested distribution ---- | ||
# This is called whenever the inputs change. The output functions | ||
# defined below then use the value computed from this expression | ||
d <- reactive({ | ||
dist <- switch(input$dist, | ||
norm = rnorm, | ||
unif = runif, | ||
lnorm = rlnorm, | ||
exp = rexp, | ||
rnorm) | ||
|
||
dist(input$n) | ||
}) | ||
|
||
# Generate a plot of the data ---- | ||
# Also uses the inputs to build the plot label. Note that the | ||
# dependencies on the inputs and the data reactive expression are | ||
# both tracked, and all expressions are called in the sequence | ||
# implied by the dependency graph. | ||
output$plot <- renderPlot({ | ||
dist <- input$dist | ||
n <- input$n | ||
|
||
hist(d(), | ||
main = paste("r", dist, "(", n, ")", sep = ""), | ||
col = "#75AADB", border = "white") | ||
}) | ||
|
||
# Generate a summary of the data ---- | ||
output$summary <- renderPrint({ | ||
summary(d()) | ||
}) | ||
|
||
# Generate an HTML table view of the data ---- | ||
output$table <- renderTable({ | ||
d() | ||
}) | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These packages aren't needed anymore, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're still used in
test-test-module
, actually.