Skip to main content

test-shiny-app

Test Shiny applications using shinytest2 for end-to-end browser tests and testServer() for unit-testing module server logic. Covers snapshot testing, CI integration, and mocking external services. Use when adding tests to an existing Shiny application, setting up a testing strategy for a new Shiny project, writing regression tests before refactoring Shiny code, or integrating Shiny app tests into CI/CD pipelines.

Jump to install

Source facts

Repository
pjt222/agent-almanac
Last source activity
September 2, 2026 at 08:44
Detected SKILL.md language
English
Stars
34
Forks
4

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
test-shiny-app
description
Test Shiny applications using shinytest2 for end-to-end browser tests and testServer() for unit-testing module server logic. Covers snapshot testing, CI integration, and mocking external services. Use when adding tests to an existing Shiny application, setting up a testing strategy for a new Shiny project, writing regression tests before refactoring Shiny code, or integrating Shiny app tests into CI/CD pipelines.
license
MIT
allowed-tools
Read Write Edit Bash Grep Glob
metadata
{"author":"Philipp Thoss","version":"1.0","domain":"shiny","complexity":"intermediate","language":"R","tags":"shiny, testing, shinytest2, testServer, snapshot, CI","locale":"de","source_locale":"en","source_commit":"1d84967e5","fence_basis_commit":"1d84967e5","translator":"(untranslated stub)","translation_date":"2026-08-11"}
# Test Shiny App Set up comprehensive testing for Shiny applications using shinytest2 (end-to-end) and testServer() (unit tests). ## When to Use - Adding tests to an existing Shiny application - Setting up a testing strategy for a new Shiny project - Writing regression tests before refactoring Shiny code - Integrating Shiny app tests into CI/CD pipelines ## Inputs - **Required**: Path to the Shiny application - **Required**: Test scope (unit tests, end-to-end, or both) - **Optional**: Whether to use snapshot testing (default: yes for e2e) - **Optional**: CI platform (GitHub Actions, GitLab CI) - **Optional**: Modules to test in isolation ## Procedure ### Step 1: Install Testing Dependencies ```r install.packages("shinytest2") # For golem apps, add as a Suggests dependency usethis::use_package("shinytest2", type = "Suggests") # Set up testthat infrastructure if not present usethis::use_testthat(edition = 3) ``` **Expected:** shinytest2 installed and testthat directory structure in place. **On failure:** shinytest2 requires chromote (headless Chrome). Install Chrome/Chromium on the system. On WSL: `sudo apt install -y chromium-browser`. Verify with `chromote::find_chrome()`. ### Step 2: Write testServer() Unit Tests for Modules Create `tests/testthat/test-mod_dashboard.R`: ```r test_that("dashboard module filters data correctly", { testServer(dataFilterServer, args = list( data = reactive(iris), columns = c("Species", "Sepal.Length") ), { # Set inputs session$setInputs(column = "Species") session$setInputs(value_select = "setosa") session$setInputs(apply = 1) # Check output result <- filtered() expect_equal(nrow(result), 50) expect_true(all(result$Species == "setosa")) }) }) test_that("dashboard module handles empty data", { testServer(dataFilterServer, args = list( data = reactive(iris[0, ]), columns = c("Species") ), { # Module should not error on empty data expect_no_error(session$setInputs(column = "Species")) }) }) ``` Key patterns: - `testServer()` tests module server logic without a browser - Pass reactive arguments via the `args` list - Use `session$setInputs()` to simulate user interactions - Access reactive return values directly by name - Test edge cases: empty data, NULL inputs, invalid values **Expected:** Module tests pass with `devtools::test()`. **On failure:** If `testServer()` errors with "not a module server function", ensure the function uses `moduleServer()` internally. If `session$setInputs()` doesn't trigger reactives, add `session$flushReact()` after setting inputs. ### Step 3: Write shinytest2 End-to-End Tests Create `tests/testthat/test-app-e2e.R`: ```r test_that("app loads and displays initial state", { # For golem apps app <- AppDriver$new( app_dir = system.file(package = "myapp"), name = "initial-load", height = 800, width = 1200 ) on.exit(app$stop(), add = TRUE) # Wait for app to load app$wait_for_idle(timeout = 10000) # Check that key elements exist app$expect_values() }) test_that("filter interaction updates the table", { app <- AppDriver$new( app_dir = system.file(package = "myapp"), name = "filter-interaction" ) on.exit(app$stop(), add = TRUE) # Interact with the app app$set_inputs(`filter1-column` = "cyl") app$wait_for_idle() app$set_inputs(`filter1-apply` = "click") app$wait_for_idle() # Snapshot the output values app$expect_values(output = "table") }) ``` Key patterns: - `AppDriver$new()` launches the app in headless Chrome - Always use `on.exit(app$stop())` to clean up - Module input IDs use the format `"moduleId-inputId"` - `app$expect_values()` creates/compares snapshot files - `app$wait_for_idle()` ensures reactive updates complete **Expected:** End-to-end tests create snapshot files in `tests/testthat/_snaps/`. **On failure:** If Chrome isn't found, set `CHROMOTE_CHROME` environment variable to the Chrome binary path. If snapshots fail on CI but pass locally, check for platform-dependent rendering differences โ€” use `app$expect_values()` for data snapshots rather than `app$expect_screenshot()` for visual ones. ### Step 4: Record a Test Interactively (Optional) ```r shinytest2::record_test("path/to/app") ``` This opens the app in a browser with a recording panel. Interact with the app, then click "Save test" to auto-generate test code. **Expected:** A test file is generated in `tests/testthat/` with recorded interactions. **On failure:** If the recorder doesn't open, check that the app runs successfully with `shiny::runApp()` first. The recorder requires a working app. ### Step 5: Set Up Snapshot Management For snapshot-based tests, manage expected values: ```r # Accept new/changed snapshots after review testthat::snapshot_accept("test-app-e2e") # Review snapshot differences testthat::snapshot_review("test-app-e2e") ``` Add snapshot directories to version control: ```text tests/testthat/_snaps/ # Committed โ€” contains expected values ``` **Expected:** Snapshot files tracked in git for regression detection. **On failure:** If snapshots change unexpectedly, run `testthat::snapshot_review()` to see the diffs. Accept intentional changes with `testthat::snapshot_accept()`. ### Step 6: Integrate with CI Add to `.github/workflows/R-CMD-check.yaml` or create a dedicated workflow: ```yaml - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y chromium-browser - name: Set Chrome path run: echo "CHROMOTE_CHROME=$(which chromium-browser)" >> $GITHUB_ENV - name: Run tests run: | Rscript -e 'devtools::test()' ``` For golem apps, ensure the app package is installed before testing: ```yaml - name: Install app package run: Rscript -e 'devtools::install()' ``` **Expected:** Tests pass in CI with headless Chrome. **On failure:** Common CI issues: Chrome not installed (add the apt-get step), display server missing (shinytest2 uses headless mode by default so this usually isn't an issue), or timeout on slow runners (increase `timeout` in `AppDriver$new()`). ## Validation - [ ] `devtools::test()` runs all tests without errors - [ ] testServer() tests cover module server logic - [ ] shinytest2 tests cover key user workflows - [ ] Snapshot files are committed to version control - [ ] Tests pass in CI environment - [ ] Edge cases tested (empty data, NULL inputs, error states) ## Common Pitfalls - **Testing UI rendering instead of logic**: Prefer `testServer()` for logic and `app$expect_values()` for data. Only use `app$expect_screenshot()` when visual appearance matters โ€” screenshots are brittle across platforms. - **Module ID format in e2e tests**: When setting module inputs via AppDriver, use `"moduleId-inputId"` format (hyphen-separated), not `"moduleId.inputId"`. - **Flaky timing**: Always call `app$wait_for_idle()` after `app$set_inputs()`. Without it, assertions may run before reactive updates complete. - **Snapshot drift**: Don't commit snapshots generated on different platforms (Mac vs Linux). Standardize on the CI platform for snapshot generation. - **Missing Chrome on CI**: shinytest2 requires Chrome/Chromium. Always include the installation step in CI workflows. ## Related Skills - `build-shiny-module` โ€” create testable modules with clear interfaces - `scaffold-shiny-app` โ€” set up app structure with testing infrastructure - `write-testthat-tests` โ€” general testthat patterns for R packages - `setup-github-actions-ci` โ€” CI/CD setup for R packages (golem apps)
View on GitHub