How do I test an observeEvent that updates an input? / How do I test a module using shinytest?

I have a shiny module that I want to test (see code below) that is part of an app that I have packaged as an R package.

The reactive value timeunit_rv holds a state in the app using the module. It can be set by several controls, and as I update one, I want the other controls to be updated too.

I would like to write two tests, one for each of the two observeEvents:

  1. Test that if I update input$tunit, then timeunit_rv is updated.
  2. Test that if I update timeunit_rv, then input$tunit is updated.

I would like to use shiny::testServer, if possible.

Questions:

  1. Am I correct that it is not possible to use shiny::testServerbecause that function does not take into account the ui, and hence running updateSelectInput(session, "tunit", selected = timeunit_rv())will not update input$tunit?
  2. If testServer does not work, how do I best test my module? Would shinytest be my best option? How would I best use shinytest to test the module in isolation from the rest of the app?
TimeUnitUI <- function(id) {
  ns <- NS(id)
  selectInput(ns("tunit"), "Unit", choices = c("s", "m", "h"), selected = "h")
}

timeUnitServer <- function(id, timeunit_rv) {
  
  moduleServer(id, function(input, output, session) {
    
    observeEvent(input$tunit, {
      timeunit_rv(input$tunit)
    })
    
    observeEvent(timeunit_rv(), {
      updateSelectInput(session, "tunit", selected = timeunit_rv())
    })

  })
}

OK, since no one has answered and I did some more research I will try to offer an answer for others myself. Please, other readers, give feedback.

  1. Yes, you are.
  2. Yes, shinytest would be the best way. In particular, I enjoy the way shinytest is used in this article, where the author uses it as part of test_that calls.

However, that article does not describe how to test it as a module and inside a package. Let's start with the latter.

Package

I guess for the purpose of this question the difference between being part of a package and not mostly relates to where you place files and how you execute them. If not part of a package, you just follow the article above and execute your test script using testthat::test_dir() or testthat::test_file(), as described here.

If part of a package, I generally place my tests in tests/testthat and execute them using devtools::test().

Module

To test a module using shinytest, I put it inside a minimal test app. The test app is only for testing purposes, so I put it inside tests/testthat. Then I instantiate my minimal test app in the test using the following code and start testing

app <- shinytest::ShinyDriver$new("minimal_test_app.R", loadTimeout = 1e+05)
expect_equal(app$getValue("whatever"),   "this_is_the_correct_value")

The next problem is that the app needs to find the code containing the module. If not inside a package, you can just use source and library inside minimal_test_app.R to get the module and the required external libraries. If inside a package, I place the command pkg_load::load_all() as the first row of minimal_test_app.R. I guess the setup is a bit similar to what is described under " Application objects created by functions" here.

Comparison to using testthat::test_server

I find that the tests run quite a bit slower when using shinytest than when using testthat::testServer, perhaps because we are using a headless browser. Therefore, I would use testthat::testServer when possible. However, in cases such as the one above, where events trigger server calculations that in turn update the UI, shinytest is the only option that I am aware of.

Test fixtures

Creating a test app such as this one is a type of test fixture, and should be cleaned up after the test.

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.