how to combine shinytest2 and ggplot2 output if sometimes expect a message instead of a plot

64 views Asked by At

my app returns a ggplot - or - a message to the user why the plot isn't generated
the mesage is created with validate(need())
I haven't been able to figure out how to make this work with shinytest2
here's the example from Robust testing updated to show what I'm trying to do

app file:

# initial example from
#   https://rstudio.github.io/shinytest2/articles/robust.html
# removed depreciated aes_string()

library(shiny)
library(ggplot2)
ui <- fluidPage(
  numericInput("n", "Number of rows", 10, 1, nrow(cars)),
  plotOutput("plot")
)
server <- function(input, output) {
  dt <- reactive({
    head(cars, input$n)
  })
  plot_obj <- reactive({
    validate(need(input$n > 0, message = "n must be strictly positive"))
    ggplot(dt(), aes(speed, dist)) + geom_point()
  })

  output$plot <- renderPlot({
    plot_obj()
  })

  exportTestValues(
    dt = dt(),
    plot_obj = plot_obj()
  )
}
plot_app <- shinyApp(ui = ui, server = server)
plot_app

test file:

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

test_that("`export`ed `plot_obj` is updated by `n`", {
  skip_if_not_installed("vdiffr")

  app <- AppDriver$new(variant = platform_variant())

  expect_n_and_plot <- function(n) {
    # Verify `n` input equals `n`
    n_val <- app$get_value(input = "n")
    expect_equal(n_val, n, expected.label = n)
    # Verify `dt()` data is first `n` lines of `cars`
    dt <- app$get_value(export = "dt")
    expect_equal(dt, head(cars, n), expected.label = paste0("head(cars, ", n, ")"))

    # Verify `plot_obj()` data is `dt()`
    plot_obj <- app$get_value(export = "plot_obj")
    expect_equal(plot_obj$data, dt, info = paste0("n = ", n))
    # Verify `plot_obj()` is consistent
    vdiffr::expect_doppelganger(paste0("cars-points-", n), plot_obj)
  }

  expect_n_and_plot(10)

  # Update `n` to 20
  app$set_inputs(n = 20)
  expect_n_and_plot(20)

  # Update `n` to -1, expect message
  app$set_inputs(n = -1)
  expect_n_and_plot(-1)
})

ideally the regression testing would capture the returned message like it would for a standard output
thanks for thinking about this

1

There are 1 answers

0
Stéphane Laurent On

A call to the validate function should be in a renderXXX function:

  output$plot <- renderPlot({
    validate(need(input$n > 0, message = "n must be strictly positive"))
    plot_obj()
  })

Then you can call app$get_values or app$get_value(output = "plot"):

> app$get_value(output = "plot")
$message
[1] "n must be strictly positive"

$call
[1] "NULL"

$type
[1] "shiny.silent.error" "validation" 

So you can do plot_value <- app$get_value(output = "plot") and then expect_equal(plot_value$message, "n must be strictly positive").


Edit

Sorry, actually you can do app$get_value(output = "plot") with your code as well. But app$get_value(export = "plot_obj") causes an error, and consequently app$get_values() causes the same error. I reported this issue to the Shiny team.