Using shinytest2 with R packages

For R packages that have Shiny applications, there are generally two ways that the applications will be present in the package. The first is to have an app.R in a subdirectory of inst/. The second way is to have a function which returns a Shiny app object.

Applications in inst/

An application could live in a subdirectory of inst/, as shown below:

/
├── DESCRIPTION
├── NAMESPACE
├── R
├── inst
│   └── sample_app
│       ├── app.R
│       └── tests
│           ├── testthat
│           │   ├── _snaps
│           │   │   └── shinytest2
│           │   │       └── 001.json
│           │   └── test-shinytest2.R
│           └── testthat.R
└── tests
    ├── testthat
    │   └── test-inst-apps.R
    └── testthat.R

In this case, you can run record_test() and test_app() as normal. After you create and run the tests, there will be a tests/ subdirectory in the application directory that stores the test scripts and results.

Since we are using {testthat} for automated tests, you would create a test driver script in tests/testthat/. In this example, it’s named test-inst-apps.R and contains the following:

# File: tests/testthat/test-inst-apps.R
library(shinytest2)

test_that("sample_app works", {
  # Don't run these tests on the CRAN build servers
  skip_on_cran()

  appdir <- system.file(package = "exPackage", "sample_app")
  test_app(appdir)
})

If the application directory is not meant to be public, it can also be located in ./tests/testthat/apps. {shinytest2} does this with many application and has appdir above point to the relative path to the application.

Application objects created by functions

The second way have an application in an R package is by having a function that returns a Shiny application object. In this example, there’s a function hello_world_app(), which lives in R/hello-world.R:

/
├── .Rbuildignore
├── DESCRIPTION
├── NAMESPACE
├── R
│   └── hello-world.R
└── tests
    ├── testthat
    │   ├── _snaps
    │   │   └── app-function
    │   │       └── 001.json
    │   └── test-app-function.R
    └── testthat.R

The function simply returns an object from shinyApp():

# File: R/hello-world.R

hello_world_app <- function() {
  utils::data(cars)
  shinyApp(
    ui = fluidPage(
      sliderInput("n", "n", 1, nrow(cars), 10),
      plotOutput("plot")
    ),
    server = function(input, output) {
      output$plot <- renderPlot({
        plot(head(cars, input$n), xlim = range(cars[[1]]), ylim = range(cars[[2]]))
      })
    }
  )
}

Once we have the object, it can be supplied directly to AppDriver$new().

# File: tests/testthat/test-app-function.R

test_that("hello-world app initial values are consistent", {
  # Don't run these tests on the CRAN build servers
  skip_on_cran()

  shiny_app <- hello_world_app()
  app <- AppDriver$new(shiny_app, name = "hello")

  app$expect_values()
})

To help create tests, you can call record_test() on your shiny application object directly. Unfortunately, the test file will not be able to be saved. Instead, the test commands can be copied into a test script manually.

Other setup steps

There are a few steps that are needed for both types of tests.

It is recommended to call shinytest2::use_shinytest2() to enable different test config set-ups.

You will need to add {shinytest2} to the Suggests section in your DESCRIPTION file.

Suggests:
    shinytest2

When all of these items are in place, you can test your package using devtools::install(); testthat::test_local() or by running R CMD check on your package. If you are using the RStudio IDE, you can also run Build -> Test Package or Build -> Check Package.

{shinytest2} requires that your package to be installed when testing. testthat::test_local() (and related wrappers) eventually call pkgload::load_all() to temporarily source the local R package. You can use test_local() to test non-{shinytest2} tests, but you will need to install your R package to safely execute your {shinytest2} tests. If not installed, it will create a confusing situation where your {shinytest2} tests are running on a different version of your R package (whichever was last installed), than the rest of your tests (the current source).

How should I test multiple applications?

You can call shinytest2::test_app() multiple times within a test script. It does not need to be wrapped within a testthat::test_that() call.

{shinytest2} tests many internal apps using the code similar to the code below:

# ./tests/testthat/test-apps.R

app_dirs <- fs::dir_ls(testthat::test_path("apps"))
lapply(app_dirs, function(app_dir) {
  shinytest2::test_app(app_dir)
})

Continuous integration

If you would like your package to be tested with every commit, you can set it up with GitHub Actions. Please see Using shinytest2 with continuous integration for inspiration.