Files
2025-11-30 08:48:12 +08:00

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)
  })
})