Testing R Code with testthat

· 3 min read · Updated March 19, 2026 · intermediate
r testing package-development testthat unit-testing

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 error
  • expect_warning(code) — checks if code produces a warning
  • expect_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 type
  • expect_s3_class(x, "classname") — checks S3 class
  • expect_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

  1. One test file per R file — keeps tests organized and easy to find
  2. Descriptive test names — explain what behavior you’re testing
  3. Test edge cases — empty vectors, NA values, extreme inputs
  4. Test one thing per expectation block — makes debugging easier
  5. Keep tests independent — each test should run on its own

See Also


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.