Skip to Content
Course content

182: Testing Shiny Applications

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time writing R packages, you're probably comfortable with testthat. Because of that, you likely have a specific misconception: "If I just move my Shiny logic into helper functions and unit test those, my app is fully tested."

I've seen this mistake a dozen times. It sounds logical—separate the "math" from the "UI," test the math, and you're golden. But here is why that fails in the real world. Imagine you have a loan calculator app where a calculate_payment() function handles the arithmetic. You write a unit test that confirms calculate_payment(1000, 0.05, 12) returns the correct value. Great. But in the actual app, you've accidentally wrapped that function in a reactive() block that depends on an input that occasionally returns NULL before the user types anything. Your unit test passes, but your app crashes with a "non-numeric argument to binary operator" error the moment it launches.

The problem is that Shiny isn't just a collection of functions; it's a reactive graph. Testing the nodes (the functions) isn't the same as testing the edges (the reactivity).

Stop Testing Functions, Start Testing User Flows

To actually sleep soundly at night, you need to test the integration of your UI and Server. This is where shinytest2 comes in. Instead of calling a function in a script, shinytest2 launches a headless version of your app and simulates a user actually clicking buttons and typing into boxes.

It uses a concept called "snapshots." Essentially, the tool takes a recording of the app's state—every input value and every output—and saves it as a reference. The next time you run the test, it compares the new state against the snapshot. If a single character in your output text changes, the test fails.

# Here is the basic workflow I use for a simple reactive app
library(shinytest2)

# 1. Start the driver (this launches the app in the background)
app <- AppDriver$new("path/to/your/app")

# 2. Simulate user interaction
app$set_inputs(loan_amount = 5000)
app$set_inputs(interest_rate = 0.03)

# 3. Check if the output matches our expected snapshot
app$expect_values() 

Handling the "Flakiness" of Dynamic Content

One thing I should warn you about: snapshots can be frustrating if your app displays something dynamic, like the current date or a random sample of data. If your app says "Report generated on 2023-10-27," your test will fail tomorrow because the date changed.

You have two choices here. First, you can mock your data so it's always the same. Second, and more commonly, you can use expect_values() but tell Shiny to ignore specific outputs that you know will change. I generally prefer mocking the data; it makes the tests deterministic and removes the "it works on my machine" headache.

When you're writing these, don't try to test every single possible input combination—that's a recipe for a slow test suite. Focus on the "happy path" (the way it's supposed to work) and the "edge cases" (the way it's likely to break, like empty inputs or extreme values). If you do that, you've covered 90% of your risk.




📋 Practical Task

Debugging a Broken Currency Converter with shinytest2

You have been handed a Currency Converter app that is supposedly "finished," but users are reporting that the conversion doesn't trigger when switching currencies. You need to write a test script to prove the bug exists and then verify the fix.

The App Logic:

  • Input: amount (Numeric)
  • Input: currency_pair (Select input: "USD to EUR", "USD to GBP")
  • Output: converted_value (Text output)

Your Task:

  1. Using the shinytest2 package, create an AppDriver for the provided app.
  2. Simulate a user entering 100 into the amount field.
  3. Simulate the user changing the currency_pair to "USD to GBP".
  4. Use expect_values() to record the state.
  5. Identify if the converted_value is updating correctly based on the input. If the test fails (the value doesn't change), you've successfully caught the bug.

Note: You will need to install shinytest2 and chromote to run this locally.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.