brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
test-shiny-appTest Shiny apps via shinytest2 for e2e browser tests + testServer() for unit-testing module server logic. Snapshot testing, CI integration, mock external services. Use → add tests to existing Shiny app, set up testing strategy for new project, regression tests before refactor, integrate into CI/CD.
Independent GitHub snapshot · Automated classification · Original documentation governs
Comprehensive testing: shinytest2 (e2e) + testServer() (unit).
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)
Got: shinytest2 installed + testthat dir structure in place.
If err: shinytest2 needs chromote (headless Chrome). Install Chrome/Chromium. WSL: sudo apt install -y chromium-browser. Verify w/ chromote::find_chrome().
Create tests/testthat/test-mod_dashboard.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"))
})
})
Patterns:
testServer() tests module server logic w/o browserargs listsession$setInputs() simulates userGot: Module tests pass devtools::test().
If err: testServer() errs "not a module server function" → fn must use moduleServer() internally. session$setInputs() doesn't trigger reactives → add session$flushReact() after.
Create tests/testthat/test-app-e2e.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")
})
Patterns:
AppDriver$new() launches in headless Chromeon.exit(app$stop()) for cleanup"moduleId-inputId"app$expect_values() creates/cmps snapshot filesapp$wait_for_idle() ensures reactive updates completeGot: E2E tests create snapshot files in tests/testthat/_snaps/.
If err: Chrome not found → set CHROMOTE_CHROME env to path. Snapshots fail CI but pass local → platform-dep rendering diffs; use app$expect_values() for data, not app$expect_screenshot() for visual.
shinytest2::record_test("path/to/app")
Opens app in browser w/ recording panel. Interact, click "Save test" → auto-gen test code.
Got: Test file generated in tests/testthat/ w/ recorded interactions.
If err: Recorder doesn't open → check app runs w/ shiny::runApp() first. Recorder needs working app.
For snapshot tests, manage expected:
# Accept new/changed snapshots after review
testthat::snapshot_accept("test-app-e2e")
# Review snapshot differences
testthat::snapshot_review("test-app-e2e")
Add snapshot dirs to VCS:
tests/testthat/_snaps/ # Committed — contains expected values
Got: Snapshot files tracked in git for regression detection.
If err: Snapshots change unexpectedly → run testthat::snapshot_review() for diffs. Accept intentional changes w/ testthat::snapshot_accept().
Add to .github/workflows/R-CMD-check.yaml | dedicated workflow:
- 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()'
Golem apps → install pkg before testing:
- name: Install app package
run: Rscript -e 'devtools::install()'
Got: Tests pass in CI w/ headless Chrome.
If err: Common: Chrome not installed (add apt-get), display server missing (shinytest2 headless default so usually no issue), timeout on slow runners (↑ timeout in AppDriver$new()).
devtools::test() runs all w/o errorstestServer() for logic + app$expect_values() for data. Only app$expect_screenshot() when visual matters — screenshots brittle across platforms."moduleId-inputId" (hyphen), NOT "moduleId.inputId".app$wait_for_idle() after app$set_inputs(). Without → assertions may run before reactive updates.build-shiny-module — create testable modules w/ clear interfacesscaffold-shiny-app — set up app structure w/ testing infrawrite-testthat-tests — general testthat patterns for R pkgssetup-github-actions-ci — CI/CD setup for R pkgs (golem apps)