How you measure the test coverage
of a Shiny app depends on where the app’s code lives and how it is
tested. Coverage is easiest to measure when the app is part of an R
package, because package_coverage() instruments the whole
package, in every R process that loads it.
Suppose a package defines its app with functions like these:
# R/app.R
app_ui <- function() {
shiny::fluidPage(
shiny::numericInput("n", "n", 1),
shiny::textOutput("out")
)
}
app_server <- function(input, output, session) {
output$out <- shiny::renderText(input$n * 2)
}
run_app <- function() {
shiny::shinyApp(app_ui(), app_server)
}testServer()shiny::testServer() runs the server function in the same
R process as the tests, so the code it runs counts towards the coverage
like any other test:
A shinytest2 AppDriver runs the app in a separate R
process. When that process loads the package it loads the copy
instrumented by covr, so the code run by the app counts as well. Create
the app with the package’s functions, either by passing the app object
to the driver:
# tests/testthat/test-app.R
test_that("the app doubles n", {
app <- shinytest2::AppDriver$new(run_app())
app$set_inputs(n = 2)
expect_equal(app$get_value(output = "out"), "4")
app$stop()
})or with an app.R in the tests that calls them,
e.g. mypkg::run_app(), and
AppDriver$new(test_path("apps/myapp")).
The app process saves its coverage when it exits, so:
Call app$stop() at the end of each test. It stops
the app gracefully, and waits up to 20 seconds for it to exit when it is
run by covr. The coverage of an app that is killed, or is still running
when the tests finish, is lost and package_coverage() warns
(with class covr_unsaved_trace).
AppDriver$new() skips the test on CRAN, which is the
default unless the NOT_CRAN environment variable is
"true". The GitHub Actions set up by
r-lib/actions/setup-r already set it; elsewhere set it
before measuring the coverage:
Use shiny_app_coverage() for an app that isn’t part of a
package, e.g. one tested with shinytest2’s test_app(), with
its tests in the app’s tests/testthat/ directory:
# app.R
library(shiny)
ui <- fluidPage(
numericInput("n", "n", 1),
textOutput("out")
)
server <- function(input, output, session) {
output$out <- renderText(triple(input$n))
}
shinyApp(ui, server)# tests/testthat/test-app.R
test_that("the app triples n", {
app <- shinytest2::AppDriver$new()
app$set_inputs(n = 2)
expect_equal(app$get_value(output = "out"), "6")
app$stop()
})By default shiny_app_coverage() runs
shinytest2::test_app(). Use its code argument
to run the tests in another way.
covr instruments the functions defined in the app’s files
(app.R, ui.R, server.R,
global.R and R/) when Shiny loads them, both
in the R session running the tests, e.g. for tests that call
testServer("../../"), and in the R processes that run the
app. To do this it adds itself to the default packages of these
processes and relies on hooks internal to Shiny. As with apps in a
package, call app$stop() so that the app saves its
coverage, and set NOT_CRAN.