Initial commit
This commit is contained in:
184
skills/testing-r-packages/references/snapshots.md
Normal file
184
skills/testing-r-packages/references/snapshots.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 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
|
||||
|
||||
```r
|
||||
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:**
|
||||
```r
|
||||
devtools::test() # Creates new snapshots
|
||||
```
|
||||
|
||||
**Review changes:**
|
||||
```r
|
||||
testthat::snapshot_review('test-name')
|
||||
```
|
||||
|
||||
**Accept changes:**
|
||||
```r
|
||||
testthat::snapshot_accept('test-name')
|
||||
```
|
||||
|
||||
**Reject changes:**
|
||||
```r
|
||||
testthat::snapshot_reject('test-name')
|
||||
```
|
||||
|
||||
**Download snapshots from GitHub CI:**
|
||||
```r
|
||||
testthat::snapshot_download_gh()
|
||||
```
|
||||
|
||||
## Snapshot Types
|
||||
|
||||
### Output Snapshots
|
||||
|
||||
Capture printed output, messages, warnings, and errors:
|
||||
|
||||
```r
|
||||
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:
|
||||
|
||||
```r
|
||||
test_that("data structure is correct", {
|
||||
expect_snapshot(str(complex_object))
|
||||
})
|
||||
```
|
||||
|
||||
### Error Snapshots
|
||||
|
||||
Capture error messages with call information:
|
||||
|
||||
```r
|
||||
test_that("errors are informative", {
|
||||
expect_snapshot(
|
||||
error = TRUE,
|
||||
my_function(invalid_input)
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
## Transform Function
|
||||
|
||||
Use `transform` to remove variable elements before comparison:
|
||||
|
||||
```r
|
||||
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:
|
||||
|
||||
```r
|
||||
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:**
|
||||
```r
|
||||
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:**
|
||||
```r
|
||||
test_that("diff output is readable", {
|
||||
withr::local_options(width = 80)
|
||||
expect_snapshot(
|
||||
waldo::compare(expected, actual)
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
**Testing printed output with messages:**
|
||||
```r
|
||||
test_that("function provides feedback", {
|
||||
expect_snapshot({
|
||||
result <- process_data(sample_data)
|
||||
print(result)
|
||||
})
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user