Skip to content

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

@davidrsch

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions