Skip to content

feat: Allow for dev packages to be dynamically loaded#402

Merged
schloerke merged 43 commits intomainfrom
pkg_test_env
Nov 18, 2025
Merged

feat: Allow for dev packages to be dynamically loaded#402
schloerke merged 43 commits intomainfrom
pkg_test_env

Conversation

@schloerke
Copy link
Copy Markdown
Collaborator

@schloerke schloerke commented Feb 10, 2025

Summary

Add support for local package development and function-based app initialization in
AppDriver$new(app_dir=). This allows testing Shiny apps without requiring package
installation and provides more flexible ways to initialize test apps.

Key Features

  1. Local Package Development Support: When testing apps within a package,
    library(<pkg>) or require(<pkg>) calls automatically execute pkgload::load_all()
    to load the local package's source code instead of requiring the package to be
    installed first.

Package installation is no longer required

  1. Function-Based App Initialization: app_dir= now accepts a function that
    returns a Shiny app object or runs an app directly, providing more flexibility in test
    setup.

Examples

Testing a Shiny app in a package without installing

Using a function to initialize your app

Example 1: Function returning a Shiny app object

test_that("App works with custom initialization", {
   app <- AppDriver$new(
     app_dir = function() {
       library(mypackage)  # Automatically loads source code if in package
       create_my_app(config = "test")
     }
   )
   # ...
 })

Example 2: Function for golem/rhino apps

 test_that("Golem app works", {
   app <- AppDriver$new(
     app_dir = function() {
       library(mygolemapp)
       run_app()  # Returns shiny.appobj
     }
   )
   # ...
 })

 Example 3: Package function directly provided
```r
 test_that("Custom app runner", {
   app <- AppDriver$new(run_app)
   # ...
 })

Example 4: Running app directly in function

  test_that("Custom app runner", {
    app <- AppDriver$new(
      function() {
        library(shiny)
        runApp(
          test.mode = TRUE, 
          shinyApp(
            ui = fluidPage("Hello"),
            server = function(input, output) {}
          )
        )
      }
    )
    # ...
  })

Technical Details:

  • Disabled on CRAN checks (testthat::is_checking()) to ensure only installed packages
    are used during R CMD check
  • Automatically detects when a function's environment belongs to the local package and
    upgrades it to load the package first
  • Shims library() and require() in the background R process to intercept package
    loading
  • When testing from within the package root, automatically disables Shiny's R/ folder
    autoloading to prevent conflicts

Benefits

  • Faster development cycle: Test changes without reinstalling the package
  • CI/CD friendly: Works seamlessly with GitHub Actions workflow updates
  • Framework agnostic: Supports vanilla Shiny, golem, rhino, and other frameworks
  • Backward compatible: Existing tests continue to work without modification

The magic bits of this PR can be found at https://github.com/rstudio/shinytest2/pull/402/files#diff-1e9839b0ee9b578e1094ebabe21a38467f417789f5b3e17fec51556b6706a851R90 where we use {pkgload} to load a local R package before calling library() or require(). This is done to mimic Shiny app behavior, not testthat testing behavior. I want the author to be required to call library() or require() for the app code to be the same both within shinytest2 testing and when run normally.

TODO:

  • Test local R package
  • Test golem app
  • Test rhino app

schloerke and others added 4 commits September 24, 2024 13:43
schloerke and others added 6 commits March 25, 2025 15:31
* main:
  fix(testthat): Use `muffle_expectation`, not `continue_test` (#418)
  v0.4.1 (#415)
  fix: default `transform=` to NULL, not itself (#413)
  Increment version number to 0.4.0.9000
  v0.4.0 (#410)
  Pre-release cleanups for shinytest 2 0.3.3 (#409)
  feat: Add `transform=` parameter to `$expect_download()` and `$expect_values()` (#403)
@schloerke schloerke changed the title WIP feat: Allow for packages to be dynamically loaded in app feat: Allow for dev packages to be dynamically loaded Nov 18, 2025
@schloerke
Copy link
Copy Markdown
Collaborator Author

Merging to move forward with a release soon

@schloerke schloerke merged commit 1d0c151 into main Nov 18, 2025
18 checks passed
@schloerke schloerke deleted the pkg_test_env branch November 18, 2025 09:30
schloerke added a commit that referenced this pull request Nov 18, 2025
* main:
  fix(snapshot): Use local testthat edition to announcing files (#419)
  feat: Allow for dev packages to be dynamically loaded (#402)
  fix(testthat): Use `muffle_expectation`, not `continue_test` (#418)
  v0.4.1 (#415)
  fix: default `transform=` to NULL, not itself (#413)
  Increment version number to 0.4.0.9000
  v0.4.0 (#410)
  Pre-release cleanups for shinytest 2 0.3.3 (#409)
  feat: Add `transform=` parameter to `$expect_download()` and `$expect_values()` (#403)
  chore!: Skip all tests using `AppDriver` during CRAN testing. Deprecate `cran=` parameters (#407)
  docs: Namespace `ChromoteSession` (#406)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants