Permalink
Cannot retrieve contributors at this time
--- | |
title: "Custom expectations" | |
output: rmarkdown::html_vignette | |
vignette: > | |
%\VignetteIndexEntry{Custom expectations} | |
%\VignetteEngine{knitr::rmarkdown} | |
%\VignetteEncoding{UTF-8} | |
--- | |
```{r setup, include = FALSE} | |
library(testthat) | |
knitr::opts_chunk$set(collapse = TRUE, comment = "#>") | |
``` | |
This vignette shows you how to create custom expectations that work identically to the built-in `expect_` functions. Since these functions will need to be loaded when your package is loaded for testing, it is recommended that `expect_` functions be defined in `test-helpers.R` in your packages `R/` directory. | |
## Creating an expectation | |
There are three main parts to writing an expectation, as illustrated by `expect_length()`: | |
```{r} | |
expect_length <- function(object, n) { | |
# 1. Capture object and label | |
act <- quasi_label(rlang::enquo(object), arg = "object") | |
# 2. Call expect() | |
act$n <- length(act$val) | |
expect( | |
act$n == n, | |
sprintf("%s has length %i, not length %i.", act$lab, act$n, n) | |
) | |
# 3. Invisibly return the value | |
invisible(act$val) | |
} | |
``` | |
## Quasi-labelling | |
The first step in any expectation is to capture the actual object, and generate a label for it to use if a failure occur. All testthat expectations support quasiquotation so that you can unquote variables. This makes it easier to generate good labels when the expectation is called from a function or within a for loop. | |
By convention, the first argument to every `expect_` function is called `object`, and you capture it's value (`val`) and label (`lab`) with `act <- quasi_label(enquo(object))`, where `act` is short for actual. | |
### Verify the expectation | |
Next, you should verify the expectation. This often involves a little computation (here just figuring out the `length`), and you should typically store the results back into the `act` object. | |
Next you call `expect()`. This has two arguments: | |
1. `ok`: was the expectation successful? This is usually easy to write | |
2. `failure_message`: What informative error message should be reported to | |
the user so that they can diagnose the problem. This is often hard to | |
write! | |
For historical reasons, most built-in expectations generate these with | |
`sprintf()`, but today I'd recommend using the | |
[glue](https://glue.tidyverse.org) package | |
### Invisibly return the input | |
Expectation functions are called primarily for their side-effects (triggering a failure), so should invisibly return their input, `act$val`. This allows expectations to be chained: | |
```{r} | |
mtcars %>% | |
expect_type("list") %>% | |
expect_s3_class("data.frame") %>% | |
expect_length(11) | |
``` | |
## `succeed()` and `fail()` | |
For expectations with more complex logic governing when success or failure occurs, you can use `succeed()` and `fail()`. These are simple wrappers around `expect()` that allow you to write code that looks like this: | |
```{r} | |
expect_length <- function(object, n) { | |
act <- quasi_label(rlang::enquo(object), arg = "object") | |
act$n <- length(act$val) | |
if (act$n == n) { | |
succeed() | |
return(invisible(act$val)) | |
} | |
message <- sprintf("%s has length %i, not length %i.", act$lab, act$n, n) | |
fail(message) | |
} | |
``` | |
## Testing your expectations | |
Use the expectations `expect_success()` and `expect_failure()` to test your expectation. | |
```{r} | |
test_that("length computed correctly", { | |
expect_success(expect_length(1, 1)) | |
expect_failure(expect_length(1, 2), "has length 1, not length 2.") | |
expect_success(expect_length(1:10, 10)) | |
expect_success(expect_length(letters[1:5], 5)) | |
}) | |
``` |