Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Testing callModule #4180

Open
alexverse opened this issue Jan 28, 2025 · 1 comment
Open

Support for Testing callModule #4180

alexverse opened this issue Jan 28, 2025 · 1 comment

Comments

@alexverse
Copy link

Context

The current Shiny documentation emphasizes that moduleServer is the recommended approach and can be tested using testServer, whereas callModule does not have direct testing support. However, many legacy Shiny applications still rely on callModule, making it difficult to refactor or maintain without the ability to write tests.

Proposed Solution

We can implement a function, testCallModule(), that enables testing for callModule by evaluating the module's server-side expression as a server function with arguments bound in its environment. This allows us to inject the testing expression into testServer.

Here's the code:

testCallModule <- function(
  module_server,
  expr,
  args = list(),
  session = MockShinySession$new()
) {
  required_args <- c("input", "output", "session")
  rlang::check_required(module_server, required_args)

  module_args <- rlang::fn_fmls(module_server) |>
    purrr::discard_at(required_args) |>
    purrr::list_modify(!!!args)
  module_body <- rlang::fn_body(module_server)

  server <- function(input, output, session) {
    rlang::env_bind(rlang::current_env(), !!!module_args)
    rlang::eval_bare(module_body)
  }

  testServer(
    app = server,
    expr = !!rlang::enexpr(expr),
    session = session
  )
}

Usage Example

Here’s how this function could be used in practice:

myModule <- function(input, output, session, prefix = "") {
  output$result <- renderText({
    paste0(prefix, toupper(input$txt))
  })
}

testCallModule(myModule, args = list(prefix = "foo"), {
  session$setInputs(txt = "bar")
  result <- output$result
  expect_equal(result, "fooBAR")
})

Request

Could this functionality be officially supported in Shiny? It offers a way to refactor and stabilize legacy code.

If the Shiny team sees value in this, I’d happily open a PR to contribute to this functionality.

@gadenbuie
Copy link
Member

Thanks for opening an issue and for providing a work around for anyone wanting to test callModule(). Before opening a PR, the next step would be to do some background research into the testing situation with callModule(). Here are some things we'd want to review:

  1. A small reprex demonstrating how callModule() can't be tested in the same way as moduleServer(). Ideally, that'd be the same module used with both functions, but only moduleServer() is able to be tested with testServer().

  2. What differentiates callModule() and moduleServer() such that testing with the second approach is possible?

  3. Could we make an internal change to testServer() or related infrastructure that could fix this issue, or are we stuck with the existing limitations?

I think it's certainly reasonable to support testing legacy code in Shiny, but we have balance our priorities and focus. If you're interested in moving this forward, having the above context would be a big help and would make it easier for us to review proposed solutions.

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

No branches or pull requests

2 participants