testthat: expectation-level unit testing for R packages
An R 📦 to make testing 😀
At a glance
- What is it?
- testthat is the R package most CRAN libraries use to test themselves, and its distinguishing feature is granularity: it reports a pass, fail or error for every expectation rather than stopping at the first broken assertion. This look at what the package actually gives you, how it fits into R CMD check, and where its design choices stop paying off.
- Who is it for?
- Adopt testthat when you are maintaining an R package that other people install from CRAN, since the per-expectation reporting and the R CMD check integration are what its users already expect, and it is a poor fit for a one-off script with no package around it.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly R, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A test run reports one result per expectation, not one per file
The design decision that separates testthat from a plain sequence of stopifnot calls is granularity of reporting. The package states that it displays test progress visually, showing a pass, a fail or an error for every expectation, and that the output is coloured in the terminal or in a recent version of RStudio. That means a suite with forty expectations produces forty results, so a developer sees how much of a function still works after one of its arguments changed, rather than a single failure that hides everything downstream of it.
The functions are built around describing what you expect a function to do, and the vocabulary includes catching errors, warnings and messages as first class outcomes. A test that expects a warning to be raised is a passing test when the warning appears and a failing test when it does not, which is the case that a bare error check usually gets wrong. The intent behind all of it is stated plainly: testing should be addictive so that you do it all the time.
The package runs inside a standard R package layout, not beside it
The repository is laid out the way an R package is expected to be, and the layout tells you where tests live and what surrounds them. There is an R directory for the package code, a src directory, a man directory for documentation, a tests directory for the test suite, a DESCRIPTION file and a NAMESPACE file, plus a NEWS.md file for release notes and a cran-comments.md file. A testthat.Rproj project file sits at the root, which gives the test suite a project file of its own, separate from the package project.
Around that core the repository carries the machinery of a package that is expected to be submitted to CRAN. There are .covrignore and codecov.yml files for coverage reporting, a .Rbuildignore file, a revdep directory for reverse dependency checks, pkgdown configuration, a vignettes directory and a _pkgdown.yml file, and GitHub workflow directories. Two newer entries are worth noting because they say something about how the project now works: an air.toml file and a .claude directory, whose names suggest that formatter configuration and automated assistant tooling have been introduced into the repository.
Installing the released version or the GitHub development version
The installation path is the standard one for an R package, with a second option for people who need a change that has not reached CRAN yet. The released version comes from the CRAN repository, and the development version is installed through pak, which has to be installed separately. The commands are short enough to state exactly.
# Install the released version from CRAN
install.packages("testthat")
# Or the development version from GitHub:
# install.packages("pak")
pak::pak("r-lib/testthat")The two routes are not interchangeable in practice. The CRAN route gives you a fixed published version, which is what you want on a machine that other people reproduce results on. The pak route tracks the repository, so the same command can install different code on different days. The README also shows that a test run fits into three contexts: informal testing on the command line, building a test suite, and using R CMD check. Which of the three you are in decides how much infrastructure you need to set up before the first expectation runs.
usethis::use_test writes the first test file and the scaffolding around it
The documented way in is through usethis, and the assumption is that you are sitting in a package directory. One call creates a test file and sets up the other infrastructure you need, and the RStudio shortcut is Cmd or Ctrl plus Shift plus T, with devtools::test() as the equivalent outside RStudio.
usethis::use_test("name")That is the entire setup step in the documentation, which is the point. A new contributor to an R package should not have to learn where test files belong, how they are named, or which boilerplate file has to exist for the runner to find them. The scaffolding the helper creates is what later lets the suite run under R CMD check without further wiring, and that matters because the check is the gate every CRAN submission passes through. The one caution is that usethis writes into a real package, so pointing it at the wrong directory leaves stray files behind rather than failing loudly.
Expectations cover errors, warnings and messages, not just return values
Most unit testing tools start from one idea: call something, compare the result. testthat adds a second axis, which is what the phrase catching errors, warnings and messages refers to. R signals conditions rather than throwing them, and a function can emit a warning and still return a perfectly good value, so a test that only compares return values will pass over behaviour that a user would notice. The package makes those signals into things a test can assert on.
This also changes what a failing run tells you. Instead of a stack trace ending in a comparison that did not hold, you get a named expectation with a pass, fail or error state attached. The error state is separate from the fail state, which is a distinction worth having: an expectation that raised an unexpected error is not the same problem as an expectation that ran and returned the wrong value, and the two send you to different parts of the code. A test file with many expectations is also a description of behaviour, readable by someone who did not write it.
Compared with base R assertions and with the xUnit lineage it grew from
The package states that it draws inspiration from the xUnit family of testing packages and from several Ruby testing libraries, naming rspec, testy, bacon and cucumber as influences. The difference in approach against base R is that base R offers stopifnot and the stop function, which abort the current expression the moment a condition is false. You get a smaller vocabulary and a shorter feedback loop for a single script, but the run stops at the first failure, and the failure surfaces as an error rather than as a tally of expectations.
The R package libraries that the Ruby and xUnit world contributed is the notion that a test file is a list of named expectations and that the framework reports on all of them. Cucumber's influence shows up in the general shape of behaviour-focused testing, where the description of what a function should do sits next to the call. Where the R implementation differs is in the fit with the language: R's condition system is a first-class signalling mechanism, so catching a warning is an ordinary expectation rather than a special case bolted on afterwards.
Release cadence is fast and the licence needs reading in the repository
Maintenance looks active by the record. The repository is not archived and the last push was on 2026-09-30. Releases move quickly on the 3.3 series: v3.3.2 on 2026-01-12, v3.3.1 on 2025-11-26 and v3.3.0 on 2025-11-13, so three tagged versions landed inside two months. A quick release line is good news for a tool that CRAN packages depend on, and it is also a reason to pin versions in your own package metadata, because a new minor release can change a deprecation or an edition requirement underneath you.
On the licence, the licence field on the repository says it is a custom licence GitHub cannot classify, with a pointer to the licence file, and the repository contains both a LICENSE file and a LICENSE.md file. Since testthat is installed into other people's systems as a dependency, the terms deserve a read before you vendor it into a closed source product, and the files in the repository are the place to do that rather than the classifier on the repository page.
Editorial conclusion
Adopt testthat when you are maintaining an R package that other people install from CRAN, since the per-expectation reporting and the R CMD check integration are what its users already expect, and it is a poor fit for a one-off script with no package around it. Before you commit, read the file named LICENSE in the repository, because the licence field on the repository says it is a custom licence GitHub cannot classify, and check the testthat edition requirements in DESCRIPTION before mixing it with older helpers in an existing suite. Pin the version you install, since the 3.3 series moved several times between November 2025 and January 2026.
Frequently asked questions
How do I install testthat for R?
The released version is installed from CRAN with install.packages("testthat"). The development version from GitHub is installed with pak::pak("r-lib/testthat"), which requires pak to be installed first.
How do I create my first test file in an R package?
Run usethis::use_test("name") from the package directory. The helper creates the test file and the surrounding infrastructure you need, and in RStudio the same suite runs with Cmd or Ctrl plus Shift plus T, or devtools::test() outside RStudio.
What does testthat report that a plain stopifnot does not?
It shows a pass, a fail or an error for every expectation instead of aborting at the first false condition, and it can catch errors, warnings and messages as expectations in their own right. Output is coloured in the terminal or in a recent version of RStudio.
How often is testthat released?
On the 3.3 series the tags are close together: v3.3.0 on 2025-11-13, v3.3.1 on 2025-11-26 and v3.3.2 on 2026-01-12. The last push was on 2026-09-30, so the repository is not archived.