Testing with testthat
Prerequisites: Error handling and defensive code
A test is code that checks your other code works. Tests catch mistakes when you change a function, and they document what the function is supposed to do. testthat is R's standard testing package.
Writing tests
library(testthat)
clean_price <- function(text) {
value <- as.numeric(gsub("[^0-9.]", "", text))
if (is.na(value)) stop("Could not parse a price from '", text, "'")
value
}
test_that("clean_price strips currency symbols and commas", {
expect_equal(clean_price("$1,299.50"), 1299.5)
expect_equal(clean_price("€15"), 15)
})
test_that("clean_price rejects text without a number", {
expect_error(clean_price("free"), "Could not parse")
})
Test passed with 2 successes 😀.
Test passed with 1 success 🥇.
(testthat adds a randomly chosen celebratory emoji, so yours may differ.)
test_that("description", { ... })groups related expectations.- Expectations check results:
expect_equal(),expect_true(),expect_identical(),expect_error(),expect_warning(),expect_length(),expect_s3_class(). expect_equalallows tiny floating-point differences, so decimals compare sensibly.
A failing test
When something breaks, testthat tells you exactly what differed:
── Failure: clean_price strips currency symbols and commas ──
clean_price("$1,299.50") (`actual`) not equal to 1299 (`expected`).
`actual`: 1300
`expected`: 1299
Organizing tests
In a package (Level 3) or a larger project, tests live in
tests/testthat/test-*.R files, and testthat::test_dir("tests/testthat")
or devtools::test() runs them all.
What to test
Test the functions that clean and transform data — they're where subtle bugs hide. Include edge cases: empty input, missing values, unexpected text, and the boundaries of ranges.
Common mistakes
- Testing with the same numbers you used while writing the function. Add awkward cases you didn't think of at first.
- Comparing decimals with
expect_identical, which fails on tiny rounding differences — useexpect_equal.