Executing `.onLoad()` in different test instances

Consider a dummy a package foo that's intended to be used in Quarto or R Markdown. The package contains the following R script:

.onLoad <- function() print(rmarkdown::metadata$title)

I'd like to test that feature:

# helpers

local_stuff <- function(title, env = parent.frame()) {
  temp_dir <- withr::local_tempdir(.local_envir = env)
  temp_file <- withr::local_tempfile(
    lines = glue::glue("
    ---
    title: {title}
    ---
    ```{{r test}}
    library(foo)
    ```
    "),
    fileext = ".Rmd",
    tmpdir = temp_dir,
    .local_envir = env
  )
  
  rmarkdown::render(temp_file, clean = FALSE, quiet = TRUE)
  temp_dir
}

read_local_md <- function(dir) {
  file <- list.files(dir, pattern = "\\.md$", full.names = TRUE)
  cat(readr::read_file(file))
}
testthat::test_that("", {
  testthat::local_edition(3)
  
  dir <- local_stuff("bar")
  testthat::expect_snapshot(read_local_md(dir))
  
  dir <- local_stuff("baz")
  testthat::expect_snapshot(read_local_md(dir))
})
#> Can't compare snapshot to reference when testing interactively.
#> ā„¹ Run `devtools::test()` or `testthat::test_file()` to see changes.
#> Current value:
#> Code
#>   read_local_md(dir)
#> Output
#>   ---
#>   title: bar
#>   ---
#>   
#>   ```r
#>   library(foo)
#>   #> [1] "bar"
#>   ```
#> Can't compare snapshot to reference when testing interactively.
#> ā„¹ Run `devtools::test()` or `testthat::test_file()` to see changes.
#> Current value:
#> Code
#>   read_local_md(dir)
#> Output
#>   ---
#>   title: baz
#>   ---
#>   
#>   ```r
#>   library(foo)
#>   ```
#> ā”€ā”€ Skip:  ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€
#> Reason: empty test

Created on 2024-02-02 by the reprex package (v2.0.1)

As you can see, the first snapshot captures the title but not the second one, meaning that .onLoad() was executed once, when rendering for the first time.

Is there a way to unload the package from within .onLoad() when it's done with the rendering so that it could run .onLoad() in every snapshot? Defering a detach doesn't make R happy.

detach() does not unload a package, unloadNamespace() does, but it does not seem like a good idea to me.

To test things that happen during loading, load the package in a subprocess, and then check the output:

callr::r(function() loadNamespace(...))
1 Like

How would you use it in my example? Would you replace lines with:

glue::glue("
  ---
  title: {title}
  ---
  ```{{r test}}
  foo <- callr::r(function() loadNamespace('foo'))
  foo$.onLoad()
  ```
")

It does not have to be in an Rmd for testing:

āÆ callr::r(function() library(tidyverse), stdout = tmp <- tempfile(), stderr = "2>&1")
āÆ readLines(tmp)
 [1] "ā”€ā”€ Attaching core tidyverse packages ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ tidyverse 2.0.0 ā”€ā”€"
 [2] "āœ” dplyr     1.1.4     āœ” readr     2.1.4"
 [3] "āœ” forcats   1.0.0     āœ” stringr   1.5.1"
 [4] "āœ” ggplot2   3.4.4     āœ” tibble    3.2.1"
 [5] "āœ” lubridate 1.9.3     āœ” tidyr     1.3.0"
 [6] "āœ” purrr     1.0.2     "
 [7] "ā”€ā”€ Conflicts ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ tidyverse_conflicts() ā”€ā”€"
 [8] "āœ– dplyr::filter() masks stats::filter()"
 [9] "āœ– dplyr::lag()    masks stats::lag()"
[10] "ā„¹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors"

But if you really want to put it in an .Rmd, you can do that as well, and call knit() from a subprocess.

2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.