Part 2
withrcovrIn “Function Design Best Practices” we added input validation with better error messages.
Our snapshot tests captured the old error output - they will now fail.
── Failure (test-cpue.R:14:3): cpue error message is informative ──────────────
Snapshot of `cpue("not a number", 10)` has changed:
old[3] == " non-numeric argument to binary operator"
new[3] == " catch must be numeric, not character"
The test is doing its job: it detected that your output changed.
Now you decide: is this the output you intended?
test() - snapshot tests failsnapshot_review() - inspect the diff.md files in tests/testthat/_snaps/This cycle will repeat throughout the course. That is normal and expected.
A test that changes global state can affect tests that run after it, AND it can affect the user’s session if they run tests interactively.
Global state includes:
getOption())The fix: make every change local to the test that needs it.
The withr package provides helpers to make temporary changes that are automatically reversed.
Aside: Why do we set type = "Suggests" for withr?
withr_ helpers:| Prefix | Scope | Use for |
|---|---|---|
local_* |
Runs until the current environment exits | Inside test_that() |
with_* |
Runs for one expression | Wrapping a single call |
examples:
cpue() reads verbosity from an option:
Tests that set options(fishr.verbose = TRUE) must restore the original value afterward - or they will leak into other tests.
local_optionstest_that("cpue uses verbosity when option set to TRUE", {
withr::local_options(fishr.verbose = TRUE) # reset when this block exits
expect_snapshot(cpue(100, 10))
})
test_that("cpue is not verbose when option set to FALSE", {
withr::local_options(fishr.verbose = FALSE) # reset when this block exits
expect_silent(cpue(100, 10))
})with_options for a single assertionProvide options in a list
Add the same verbose argument to biomass_index() and write clean tests for it.
R/biomass.R to read from getOption("fishr.verbose", FALSE)use_test("biomass")withr::local_options()test() and accept any new snapshotsHow would you test that the verbose message is correct?
test_that("biomass_index uses verbosity when set as an option", {
withr::local_options(fishr.verbose = TRUE)
expect_snapshot(biomass_index(cpue = 5, area_swept = 100))
})
test_that("biomass_index verbosity falls back to FALSE when not set", {
withr::local_options(fishr.verbose = NULL)
expect_no_message(biomass_index(cpue = 5, area_swept = 100))
})| Helper | Resets |
|---|---|
local_options() / with_options() |
R options |
local_envvar() / with_envvar() |
Environment variables |
local_dir() / with_dir() |
Working directory |
local_tempfile() / with_tempfile() |
Temp file (deleted after) |
local_tempdir() / with_tempdir() |
Temp directory (deleted after) |
local_seed() / with_seed() |
Random seed |
Where have we already altered the global state in our test helpers?
tests/testthat/helper.R calls set.seed() directly - this sets the seed for the entire session.