3.6 KiB
Snapshot Testing
Snapshot tests record expected output in human-readable files rather than inline code. They are ideal for:
- Complex output that's difficult to verify programmatically
- User-facing messages, warnings, and errors
- Mixed output types (printed text + messages + warnings)
- Binary formats like plots
- Text with complex formatting
Basic Usage
test_that("error messages are helpful", {
expect_snapshot(
my_function(bad_input)
)
})
The first run creates tests/testthat/_snaps/{test-file}/{test-name}.md containing the captured output.
Snapshot Workflow
Initial creation:
devtools::test() # Creates new snapshots
Review changes:
testthat::snapshot_review('test-name')
Accept changes:
testthat::snapshot_accept('test-name')
Reject changes:
testthat::snapshot_reject('test-name')
Download snapshots from GitHub CI:
testthat::snapshot_download_gh()
Snapshot Types
Output Snapshots
Capture printed output, messages, warnings, and errors:
test_that("function produces expected output", {
expect_snapshot({
print(my_data)
message("Processing complete")
warning("Non-critical issue")
})
})
Value Snapshots
Capture the structure of R objects:
test_that("data structure is correct", {
expect_snapshot(str(complex_object))
})
Error Snapshots
Capture error messages with call information:
test_that("errors are informative", {
expect_snapshot(
error = TRUE,
my_function(invalid_input)
)
})
Transform Function
Use transform to remove variable elements before comparison:
test_that("output is stable", {
expect_snapshot(
my_api_call(),
transform = function(lines) {
# Remove timestamps
gsub("\\d{4}-\\d{2}-\\d{2}", "[DATE]", lines)
}
)
})
Common uses:
- Remove timestamps or session IDs
- Normalize file paths
- Strip API keys or tokens
- Remove stochastic elements
Variants
Use variant for platform-specific or R-version-specific snapshots:
test_that("platform-specific behavior", {
expect_snapshot(
system_specific_function(),
variant = tolower(Sys.info()[["sysname"]])
)
})
Variants save to _snaps/{variant}/{test}.md instead of _snaps/{test}.md.
Best Practices
- Commit snapshots to git - They are part of your test suite
- Review snapshot diffs carefully - Ensure changes are intentional
- Keep snapshots focused - One concept per snapshot
- Use transform for stability - Remove variable elements
- Update snapshots explicitly - Never auto-accept in CI
- Fail on new snapshots in CI - testthat 3.3.0+ does this automatically
Snapshot Files
Snapshots are stored as markdown files in tests/testthat/_snaps/:
tests/testthat/
├── test-utils.R
└── _snaps/
├── test-utils.md
└── windows/ # variant snapshots
└── test-utils.md
Each snapshot includes:
- Test name as heading
- Code that generated the output
- Captured output
Common Patterns
Testing error messages:
test_that("validation errors are clear", {
expect_snapshot(error = TRUE, {
validate_input(NULL)
validate_input("wrong type")
validate_input(numeric())
})
})
Testing side-by-side comparisons:
test_that("diff output is readable", {
withr::local_options(width = 80)
expect_snapshot(
waldo::compare(expected, actual)
)
})
Testing printed output with messages:
test_that("function provides feedback", {
expect_snapshot({
result <- process_data(sample_data)
print(result)
})
})