rstudio/shinytest

Shinytest for a package

Closed this issue · 12 comments

I have several packages containing a shiny app.
I am using testthat for unit testing.
I try to include the UI in the tests.
In the documentation there is nothing about running shinytest with testthat for a Package (https://rstudio.github.io/shinytest/articles/ci.html#testing-applications-in-a-package -> "Coming soon").

What should be the process to do it? (should I put an app.R file in testthat folder? Is it advised to use relative paths for snapshots, etc.).

I have identified 2 difficulties:

  • passing parameters : because we need to provide app.R, this file should be generated during the tests with the expected parameters (in my case they are generated during the tests)
  • providing the expected results: my app.R file is generated in temp folder, so I need to copy paste the expected folder in the temp to run the tests.

It s definitely not easy to use :-(

@wch any plan for this issue ? It s quite difficult to use shiny in a package if we can t test the front...

wch commented

Hi @pommedeterresautee, I have some rough ideas that haven't been written up yet, but I will do so in the next few weeks. Are your applications generated by a function in the package, or is all the code in a app.R (or similar)? If it's the latter, where are the application files stored in your package?

Thanks for your answer.
In my case I have a file with a content like that:

#' Decription
#'
#' Blabla
#'
#' @param ...
#' @export
some_name <- function(parameter1, ...) {
  shinyApp(ui = get_shiny_ui(...),
           server = get_shiny_server(...),
           options = list(port = port, host = host),
           onStart = function() {
             # https://stackoverflow.com/a/47014053/817158
             # close database when the main window is closed
             onStop(function(){
               message("close connection to database")
               dbDisconnect(db_connection)
             })
           }
  )
}

Just a +1 on this one. We're using a shiny based RStudio add-in and are trying to figure out the best way to test it. The app is launched from a function and can be seen here: https://github.com/Crunch-io/rcrunch/blob/listDatasets-improvements/R/gadgets.R

So far my thoughts are:

  • Break out as much code as possible from the app and test normally
  • Use a mocked input object to generate the expected return from the server function (this doesn't seem like a good idea because the environment returned by that function is quite complicated)
  • Pull the server and ui objects out of the function into a separate folder within the app and test them with shinytest
  • Write an expect_launch testthat expectation which would at least cover whether the app was launched.

But none of those seem super great, I'd be grateful for any thoughts you had about the best approach.

@wch I have some bandwidth at work and would be happy to take a stab at this problem. Do you have any guidance about how you were envisioning the feature?

Thanks!

wch commented

I've created an example package here: https://github.com/rstudio/shinytestPackageExample

The essence of it is very simple: just create an app.R which calls your the function, and then test that.

Please note that the README currently links to a page in the shinytest site which doesn't yet exist.

@gshotwell, @pommedeterresautee I'd appreciate feedback about this approach to testing.

Thanks! I think this looks like a great (and super simple solution). I'll have to figure out how to integrate it with our http mocking procedure which should be doable. I'll let you know how it goes.

Okay I had a chance to try out that testing model and overall I think it works well. A couple of things which caused me to have to think a bit:

  • Since the app is run in a new session, it can be hard to figure out what's going on in the environment that the app is running in. Our rstudio addin is designed to work with the user's environment, as well as an external API, and so it was a bit tricky to figure out what I needed to do to mock the relevant parts of the user's environment. I'm not sure if there's a way to make this easier, but I wanted to do things like send print statements from the app.R file to the R console, or use a browser() call. I ended up using stop(getwd()) for this purpose which worked, but wasn't the most intuitive

  • You can only test functions which return a shinyApp object. This makes sense, but I think a lot of gadgets follow this pattern and call runGadget within the function. The way I got this to work was to have two functions, one which returned the shinyApp object and the second which was just the runGadget call. This isn't a problem exactly, but might be worth handling with an error.

@wch I have an App that has the same structure as your shinytestPackageExample in the test-app-file mode. It starts loading the app but it crashes because it dosen't find any of the other functions in the package (in the R/ folder). Am I missing something?
I also test it in your example creating in the R folder a new file with a function that is called from the app.R. This works when running the app, but crashes when testinng becues it can not find the function.

We're writing up general testing strategies at https://mastering-shiny.org/scaling-testing.html