1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
---
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 in order for your tests to work, we recommend putting them in an appropriately named helper file, i.e. `tests/testthat/helper-expectations.R`.
## Expectation basics
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)
}
```
|