R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
182: Testing Shiny Applications
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:
- Using the
shinytest2package, create anAppDriverfor the provided app. - Simulate a user entering
100into theamountfield. - Simulate the user changing the
currency_pairto "USD to GBP". - Use
expect_values()to record the state. - Identify if the
converted_valueis 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.
There are no comments for now.