Testing R Code with testthat
Testing is a critical part of developing reliable R code. The testthat package provides an intuitive framework for writing and running unit tests, particularly for R packages. This guide walks you through setting up testthat, writing your first tests, and integrating testing into your development workflow.
Installing testthat
Install testthat from CRAN or install the latest development version from GitHub:
# From CRAN
install.packages("testthat")
# Development version
install.packages("devtools")
devtools::install_github("r-lib/testthat")
Load the package with:
library(testthat)
Package Structure for Testing
The testthat package works best within an R package structure. Your package directory should look like this:
mypackage/
├── R/
│ ├── function1.R
│ └── function2.R
├── tests/
│ ├── testthat.R
│ └── testthat/
│ ├── test-function1.R
│ └── test-function2.R
└── DESCRIPTION
The tests/testthat.R file initializes the testing infrastructure:
# tests/testthat.R
library(testthat)
library(mypackage)
test_check("mypackage")
All test files go in tests/testthat/ and should start with test-.
Understanding Expectations
Expectations are the building blocks of tests. They check whether a result matches what you expect. The basic syntax is:
expect_* (object, expected)
Here are the most commonly used expectation functions:
Equality Expectations
expect_equal(x, y)— tests for near equality (good for floating-point numbers)expect_identical(x, y)— tests for exact equality
expect_equal(2 + 2, 4)
# [1] TRUE
expect_identical(c(1L, 2L, 3L), 1:3)
# [1] TRUE
Error and Warning Expectations
expect_error(code)— checks if code throws an errorexpect_warning(code)— checks if code produces a warningexpect_silent(code)— checks if code runs without errors or warnings
expect_error(log("not a number"))
expect_error(stop("custom error message"), "custom error message")
expect_warning(log(-1))
expect_silent(2 + 2)
Type Expectations
expect_type(x, type)— checks base R typeexpect_s3_class(x, "classname")— checks S3 classexpect_s4_class(x, "classname")— checks S4 class
expect_type(c(1, 2, 3), "double")
expect_type(1:3, "integer")
expect_s3_class(data.frame(x = 1:3), "data.frame")
Writing Your First Test
Use test_that() to group related expectations into a test:
# R function to test
get_sum <- function(x) {
if (!is.numeric(x)) {
stop("Input must be numeric")
}
sum(x)
}
# Test file: tests/testthat/test-get_sum.R
test_that("get_sum calculates the sum correctly", {
expect_equal(get_sum(c(1, 2, 3)), 6)
expect_equal(get_sum(c(0, 0)), 0)
expect_equal(get_sum(-5), -5)
})
test_that("get_sum handles invalid inputs", {
expect_error(get_sum("not numeric"))
expect_error(get_sum(NULL))
})
Each test_that() block describes what you’re testing, followed by the expectations that must pass.
Running Your Tests
Run tests during development using one of these methods:
# From your package root
devtools::test()
# Or using testthat directly
testthat::test_local()
# Run a specific test file
testthat::test_file("tests/testthat/test-get_sum.R")
When all tests pass, you’ll see output like:
✔ | OK FAILED SKIPPED
1 | 1
[1] TRUE
When a test fails, testthat shows you exactly which expectation failed and what went wrong.
Testing Conditions
Use expect_condition(), expect_warning(), and expect_message() to test for specific conditions:
test_that("log produces warning for negative input", {
expect_warning(log(-1), "NaNs produced")
})
test_that("function produces message", {
expect_message(hello(), "Hello!")
})
Snapshot Testing
For outputs that change infrequently (like complex printed output), use snapshot tests:
test_that("print output matches snapshot", {
expect_snapshot_output(print(head(mtcars)))
})
The first run saves the output; subsequent runs compare against the saved snapshot.
Best Practices
- One test file per R file — keeps tests organized and easy to find
- Descriptive test names — explain what behavior you’re testing
- Test edge cases — empty vectors, NA values, extreme inputs
- Test one thing per expectation block — makes debugging easier
- Keep tests independent — each test should run on its own
See Also
- Building R Packages — setting up package structure
- Writing R Functions — best practices for function design
- R Debugging — techniques for finding and fixing bugs
Start small with a few tests for your most important functions. As your package grows, testthat scales with you, making it easy to maintain confidence in your code.