Skip to content

Reconsider recursive test discovery for application-oriented workflows (particularly Shiny) #2341

Description

@davidrsch

Hi,

I’m reopening a discussion related to #916, but from a slightly different angle.

I understand the rationale for keeping testthat::test_dir() flat:

  • deterministic ordering,
  • simple fixture semantics,
  • explicitness over discovery magic.

That model makes a lot of sense for R packages, where tests are often organized around exported APIs and tests/testthat/ effectively represents a single suite.

However, I wonder whether the broader use of testthat in application-oriented workflows might justify revisiting this.

In the Shiny ecosystem, testthat increasingly underpins multiple layers of testing:

  • pure unit tests for extracted logic,
  • shiny::testServer() for module/server logic,
  • shinytest2 for integration and snapshot testing.

As applications grow, these tend to group naturally by concern. For example:

tests/testthat/
  unit/
    test-utils.R
    test-validation.R
  modules/
    test-auth.R
    test-upload.R
  integration/
    test-login-flow.R
  _snaps/

This structure maps well to how larger Shiny apps evolve:

  • reusable helpers and business logic,
  • module-level reactive/server tests,
  • end-to-end user-flow tests.

The current recommendation seems to be flattening all test files into the root:

tests/testthat/
  test-utils.R
  test-validation.R
  test-auth.R
  test-upload.R
  test-login-flow.R

which works, but becomes harder to navigate as the number of tests grows.

To be clear, I’m not suggesting changing defaults.

But would there be interest in supporting an opt-in mode like:

test_dir("tests/testthat", recursive = TRUE)

with constrained semantics, for example:

  • default remains FALSE,
  • execution order defined lexically over relative paths,
  • fixture files (helper-*, setup-*, teardown-*) remain scoped exactly as they are today (i.e. directory-local, matching current test_file() behavior).

This is already possible to implement manually with recursive file discovery + test_file(), so this is less about adding new capability and more about standardizing a pattern that seems increasingly relevant for larger app-oriented projects.

I’m curious whether maintainers still see recursive discovery as fundamentally outside the scope of testthat, or whether the growing use of testthat as a general testing runner (not just for packages) changes that perspective.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions