
testing-r-packages
Best practices for writing R package tests using testthat version 3+. Use when writing, organizing, or improving tests for R packages. Covers test structure, expectations, fixtures, snapshots, mocking, and modern testthat 3 patterns including self-sufficient tests, proper cleanup with withr, and snapshot testing.
- Installs
- 749
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/posit-dev/skills --skill testing-r-packages- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads testing-r-packages automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Testing R Packages with testthat
Modern best practices for R package testing using testthat 3+.
Initial Setup
Initialize testing with testthat 3rd edition:
usethis::use_testthat(3)
This creates tests/testthat/ directory, adds testthat to DESCRIPTION Suggests with Config/testthat/edition: 3, and creates tests/testthat.R.
File Organization
Mirror package structure:
- Code in
R/foofy.R→ tests intests/testthat/test-foofy.R - Use
usethis::use_r("foofy")andusethis::use_test("foofy")to create paired files
Special files:
helper-*.R- Helper functions and custom expectations, sourced before testssetup-*.R- Run duringR CMD checkonly, not duringload_all()fixtures/- Static test data files accessed viatest_path()
Test Structure
Tests follow a three-level hierarchy: File → Test → Expectation
Standard Syntax
test_that("descriptive behavior", {
result <- my_function(input)
expect_equal(result, expected_value)
})
Test descriptions should read naturally and describe behavior, not implementation.
BDD Syntax (describe/it)
For behavior-driven development, use describe() and it():
describe("matrix()", {
it("can be multiplied by a scalar", {
m1 <- matrix(1:4, 2, 2)
m2 <- m1 * 2
expect_equal(matrix(1:4 * 2, 2, 2), m2)
})
it("can be transposed", {
m <- matrix(1:4, 2, 2)
expect_equal(t(m), matrix(c(1, 3, 2, 4), 2, 2))
})
})
Key features:
describe()groups related specifications for a componentit()defines individual specifications (liketest_that())- Supports nesting for hierarchical organization
it()without code creates pending test placeholders
Use describe() to verify you implement the right things, use test_that() to ensure you do things right.
See references/bdd.md for comprehensive BDD patterns, nested specifications, and test-first workflows.
Running Tests
Three scales of testing:
Micro (interactive development):
devtools::load_all()
expect_equal(foofy(...), expected)
Mezzo (single file):
testthat::test_file("tests/testthat/test-foofy.R")
# RStudio: Ctrl/Cmd + Shift + T
Macro (full suite):
devtools::test() # Ctrl/Cmd + Shift + T
devtools::check() # Ctrl/Cmd + Shift + E
Core Expectations
Equality
expect_equal(10, 10 + 1e-7) # Allows numeric tolerance
expect_identical(10L, 10L) # Exact match required
expect_all_equal(x, expected) # Every element matches (v3.3.0+)
Errors, Warnings, Messages
expect_error(1 / "a")
expect_error(bad_call(), class = "specific_error_class")
expect_no_error(valid_call())
expect_warning(deprecated_func())
expect_no_warning(safe_func())
expect_message(informative_func())
expect_no_message(quiet_func())
Pattern Matching
expect_match("Testing is fun!", "Testing")
expect_match(text, "pattern", ignore.case = TRUE)
Structure and Type
expect_length(vector, 10)
expect_type(obj, "list")
expect_s3_class(model, "lm")
expect_s4_class(obj, "MyS4Class")
expect_r6_class(obj, "MyR6Class") # v3.3.0+
expect_shape(matrix, c(10, 5)) # v3.3.0+
Sets and Collections
expect_setequal(x, y) # Same elements, any order
expect_contains(fruits, "apple") # Subset check (v3.2.0+)
expect_in("apple", fruits) # Element in set (v3.2.0+)
expect_disjoint(set1, set2) # No overlap (v3.3.0+)
Logical
expect_true(condition)
expect_false(condition)
expect_all_true(vector > 0) # All elements TRUE (v3.3.0+)
expect_all_false(vector < 0) # All elements FALSE (v3.3.0+)
Design Principles
1. Self-Sufficient Tests
Each test should contain all setup, execution, and teardown code:
# Good: self-contained
test_that("foofy() works", {
data <- data.frame(x = 1:3, y = letters[1:3])
result <- foofy(data)
expect_equal(result$x, 1:3)
})
# Bad: relies on ambient state
dat <- data.frame(x = 1:3, y = letters[1:3])
test_that("foofy() works", {
result <- foofy(dat) # Where did 'dat' come from?
expect_equal(result$x, 1:3)
})
2. Self-Contained Tests (Cleanup Side Effects)
Use withr to manage state changes:
test_that("function respects options", {
withr::local_options(my_option = "test_value")
withr::local_envvar(MY_VAR = "test")
withr::local_package("jsonlite")
result <- my_function()
expect_equal(result$setting, "test_value")
# Automatic cleanup after test
})
Common withr functions:
local_options()- Temporarily set optionslocal_envvar()- Temporarily set environment variableslocal_tempfile()- Create temp file with automatic cleanuplocal_tempdir()- Create temp directory with automatic cleanuplocal_package()- Temporarily attach package
3. Plan for Test Failure
Write tests assuming they will fail and need debugging:
- Tests should run independently in fresh R sessions
- Avoid hidden dependencies on earlier tests
- Make test logic explicit and obvious
4. Repetition is Acceptable
Repeat setup code in tests rather than factoring it out. Test clarity is more important than avoiding duplication.
5. Use devtools::load_all() Workflow
During development:
- Use
devtools::load_all()instead oflibrary() - Makes all functions available (including unexported)
- Automatically attaches testthat
- Eliminates need for
library()calls in tests
Snapshot Testing
For complex output that's difficult to verify programmatically, use snapshot tests. See references/snapshots.md for complete guide.
Basic pattern:
test_that("error message is helpful", {
expect_snapshot(
error = TRUE,
validate_input(NULL)
)
})
Snapshots stored in tests/testthat/_snaps/.
Workflow:
devtools::test() # Creates new snapshots
testthat::snapshot_review('name') # Review changes
testthat::snapshot_accept('name') # Accept changes
Test Fixtures and Data
Three approaches for test data:
1. Constructor functions - Create data on-demand:
new_sample_data <- function(n = 10) {
data.frame(id = seq_len(n), value = rnorm(n))
}
2. Local functions with cleanup - Handle side effects:
local_temp_csv <- function(data, env = parent.frame()) {
path <- withr::local_tempfile(fileext = ".csv", .local_envir = env)
write.csv(data, path, row.names = FALSE)
path
}
3. Static fixture files - Store in fixtures/ directory:
data <- readRDS(test_path("fixtures", "sample_data.rds"))
See references/fixtures.md for detailed fixture patterns.
Mocking
Replace external dependencies during testing using local_mocked_bindings(). See references/mocking.md for comprehensive mocking strategies.
Basic pattern:
test_that("function works with mocked dependency", {
local_mocked_bindings(
external_api = function(...) list(status = "success", data = "mocked")
)
result <- my_function_that_calls_api()
expect_equal(result$status, "success")
})
Common Patterns
Testing Errors with Specific Classes
test_that("validation catches errors", {
expect_error(
validate_input("wrong_type"),
class = "vctrs_error_cast"
)
})
Testing with Temporary Files
test_that("file processing works", {
temp_file <- withr::local_tempfile(
lines = c("line1", "line2", "line3")
)
result <- process_file(temp_file)
expect_equal(length(result), 3)
})
Testing with Modified Options
test_that("output respects width", {
withr::local_options(width = 40)
output <- capture_output(print(my_object))
expect_lte(max(nchar(strsplit(output, "\n")[[1]])), 40)
})
Testing Multiple Related Cases
test_that("str_trunc() handles all directions", {
trunc <- function(direction) {
str_trunc("This string is moderately long", direction, width = 20)
}
expect_equal(trunc("right"), "This string is mo...")
expect_equal(trunc("left"), "...erately long")
expect_equal(trunc("center"), "This stri...ely long")
})
Custom Expectations in Helper Files
# In tests/testthat/helper-expectations.R
expect_valid_user <- function(user) {
expect_type(user, "list")
expect_named(user, c("id", "name", "email"))
expect_type(user$id, "integer")
expect_match(user$email, "@")
}
# In test file
test_that("user creation works", {
user <- create_user("test@example.com")
expect_valid_user(user)
})
File System Discipline
Always write to temp directory:
# Good
output <- withr::local_tempfile(fileext = ".csv")
write.csv(data, output)
# Bad - writes to package directory
write.csv(data, "output.csv")
Access test fixtures with test_path():
# Good - works in all contexts
data <- readRDS(test_path("fixtures", "data.rds"))
# Bad - relative paths break
data <- readRDS("fixtures/data.rds")
Advanced Topics
For advanced testing scenarios, see:
- references/bdd.md - BDD-style testing with describe/it, nested specifications, test-first workflows
- references/snapshots.md - Snapshot testing, transforms, variants
- references/mocking.md - Mocking strategies, webfakes, httptest2
- references/fixtures.md - Fixture patterns, database fixtures, helper files
- references/advanced.md - Skipping tests, secrets management, CRAN requirements, custom expectations, parallel testing
testthat 3 Modernizations
When working with testthat 3 code, prefer modern patterns:
Deprecated → Modern:
context()→ Remove (duplicates filename)expect_equivalent()→expect_equal(ignore_attr = TRUE)with_mock()→local_mocked_bindings()is_null(),is_true(),is_false()→expect_null(),expect_true(),expect_false()
New in testthat 3:
- Edition system (
Config/testthat/edition: 3) - Improved snapshot testing
waldo::compare()for better diff output- Unified condition handling
local_mocked_bindings()works with byte-compiled code- Parallel test execution support
Quick Reference
Initialize: usethis::use_testthat(3)
Run tests: devtools::test() or Ctrl/Cmd + Shift + T
Create test file: usethis::use_test("name")
Review snapshots: testthat::snapshot_review()
Accept snapshots: testthat::snapshot_accept()
Find slow tests: devtools::test(reporter = "slow")
Shuffle tests: devtools::test(shuffle = TRUE)
FAQ
What does the testing-r-packages skill do?
Best practices for writing R package tests using testthat version 3+. Use when writing, organizing, or improving tests for R packages. Covers test structure, expectations, fixtures, snapshots, mocking, and modern testthat 3 patterns including self-sufficient tests, proper cleanup with withr, and snapshot testing.
How do I install testing-r-packages?
Run npx skills add https://github.com/posit-dev/skills --skill testing-r-packages in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/testing-r-packages/ (personal) or .claude/skills/testing-r-packages/ (project).
Which AI agents can use testing-r-packages?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is testing-r-packages safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains testing-r-packages?
It is published by posit-dev in posit-dev/skills. It has 749 recorded installs.
More from posit-dev/skills
quarto-authoring
posit-dev/skills
Use when the user is explicitly working with Quarto, .qmd files, quarto.yml, Quarto projects, or Quarto features such as callouts, cross-references, citations, Mermaid diagrams, extensions, websites, books, presentations, and reports. Also use for explicit migration from or comparison with R Markdown, bookdown, blogdown, xaringan, distill, or Jupyter notebooks to Quarto. Do not use for general R Markdown or related-format questions unless Quarto or migration to Quarto is explicitly mentioned.
critical-code-reviewer
posit-dev/skills
Rigorously review code or pull requests for correctness, security, accessibility, maintainability, tests, and edge cases. Use when users request a critical code review, want a guided walkthrough of findings, need implementer-facing feedback, or want to prepare, create, or submit a GitHub pull request review.
brand-yml
posit-dev/skills
Create and use brand.yml files for consistent branding across Shiny apps and Quarto documents. Covers: (1) Creating new brand.yml files, (2) Applying to Shiny (R and Python), (3) Using in Quarto, (4) Modifying existing files, and (5) Troubleshooting. Includes complete specifications and integration guides.
r-package-development
posit-dev/skills
R package development with devtools, testthat, and roxygen2. Use when the user is working on an R package, running tests, writing documentation, or building package infrastructure.
shiny-bslib
posit-dev/skills
Build modern Shiny dashboards and applications using bslib (Bootstrap 5). Use when creating new Shiny apps, modernizing legacy apps (fluidPage, fluidRow/column, tabsetPanel, wellPanel, shinythemes), or working with bslib page layouts, grid systems, cards, value boxes, navigation, sidebars, filling layouts, theming, accordions, tooltips, popovers, toasts, or bslib inputs. Assumes familiarity with basic Shiny.
describe-design
posit-dev/skills
Research a codebase and create architectural documentation describing how features or systems work. Use when the user asks to: (1) Document how a feature works, (2) Create an architecture overview, (3) Explain code structure for onboarding or knowledge transfer, (4) Research and describe a system's design. Produces markdown documents with Mermaid diagrams and stable code references suitable for humans and AI agents.
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work