Skip to content

Commit cb0f77e

Browse files
committed
More writing
1 parent 73774ae commit cb0f77e

File tree

1 file changed

+44
-25
lines changed

1 file changed

+44
-25
lines changed

vignettes/custom-expectation.Rmd

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ expect_length <- function(object, n) {
3939
}
4040
```
4141

42-
The first step in any expectation is to use `quasi_label()` to capture both the value (`$val`) of the first argument and a label (`$lab`) to use failure messages. This is a pattern that exists to support fairly esoteric testthat features; you don't need to understand, just copy and paste it 🙂.
42+
The first step in any expectation is to use `quasi_label()` to capture a "labelled value", i.e. an list that contains both the value (`$val`) for testing and a label (`$lab`) for messaging. This is a pattern that exists for fairly esoteric reasons; you don't need to understand, just copy and paste it 🙂.
4343

44-
Next, you need to fail, for each way that the `object` violates our expectation. In my experience it's easier to check for problems one by one, because that yields the most informative failure messages. Note that it's really important to `return(fail())` here. You wont see the problem when interactively testing your function because when run outside of `test_that()`, `fail()` throws an error, causing the function to terminate early. When running inside of `test_that()` however, `fail()` does not stop execution because we want to collect all failures in a given test.
44+
Next you need to check each way that `object` could be broken. In most cases, it's easier to check for problems one by one, using early returns to `fail()` when any expectation is violated as that makes it easier to write failure messages. It's good practice to state both what the object is and what you expected in your failures.
4545

46-
Finally, if the object is expected, call `pass()` with the input value (usually `act$val`). Returning the input value is good practice since expectation functions are called primarily for their side-effects (triggering a failure). This allows expectations to be chained:
46+
Also note that you need to use `return(fail())` here. You won't see the problem when interactively testing your function because when run outside of `test_that()`, `fail()` throws an error, causing the function to terminate early. When running inside of `test_that()`, however, `fail()` does not stop execution because we want to collect all failures in a given test.
47+
48+
Finally, if the object is as expected, call `pass()` with `act$val`. Returning the input value is good practice since expectation functions are called primarily for their side-effects (triggering a failure). This allows expectations to be chained:
4749

4850
```{r}
4951
mtcars |>
@@ -52,15 +54,15 @@ mtcars |>
5254
expect_length(11)
5355
```
5456

55-
## Testing your expectations
57+
### Testing your expectations
5658

57-
testthat comes with three expectations designed specifically to test expectations: `expect_success()` and `expect_failure()`:
59+
Once you've written your expectation, you need to test it, and luckily testthat comes with three expectations designed specifically to test expectations:
5860

5961
* `expect_success()` checks that your expectation emits exactly one success and zero failures.
6062
* `expect_failure()` checks that your expectation emits exactly one failure and zero successes.
6163
* `expect_failure_snapshot()` captures the failure message in a snapshot, making it easier to review if it's useful or not.
6264

63-
It's important to check that expectations return either one failure or one success because the ensures that reporting is correct. If you
65+
The first two expectations are particularly important because they ensure that your expectation reports the correct number of succeses and failures to the user.
6466

6567
```{r}
6668
test_that("expect_length works as expected", {
@@ -77,9 +79,17 @@ test_that("expect_length gives useful feedback", {
7779

7880
## Examples
7981

82+
The following sections show you a few more variations, losely based on existing testthat expectations.
83+
8084
### `expect_vector_length()`
8185

82-
For example, you could imagine a slightly more complex version that first checked if the object was a vector:
86+
Lets make `expect_length()` a bit more strict by also checking that the input is a vector. R is a bit weird that it gives a length to pretty much every object, and you can imagine not wanting this code to succeed:
87+
88+
```{r}
89+
expect_length(mean, 1)
90+
```
91+
92+
To do this we'll add an extra check that the input is either an atomic vector or a list:
8393

8494
```{r}
8595
expect_vector_length <- function(object, n) {
@@ -100,17 +110,15 @@ expect_vector_length <- function(object, n) {
100110
}
101111
```
102112

103-
To make your failure messages as actionable as possible, state both what the object is and what you expected:
104-
105113
```{r}
106114
#| error: true
107-
expect_vector_length(mean, 10)
115+
expect_vector_length(mean, 1)
108116
expect_vector_length(mtcars, 15)
109117
```
110118

111119
### `expect_s3_class()`
112120

113-
As another example, imagine if you're checking to see if an object inherits from an S3 class. In R, there's no direct way to tell if an object is an S3 object: you can confirm that it's an object, then that it's not an S4 object. So you might organise your test this way:
121+
Or imagine if you're checking to see if an object inherits from an S3 class. In R, there's no direct way to tell if an object is an S3 object: you can confirm that it's an object, then that it's not an S4 object. So you might organise your expectation this way:
114122

115123
```{r}
116124
expect_s3_class <- function(object, class) {
@@ -152,45 +160,56 @@ expect_s3_class(x3, "integer")
152160

153161
## Repeated code
154162

155-
As you write more expectations, you might discover repeated code that you want to extract out in to a helper. For example, testthat has `expect_true()`, `expect_false()`, and `expect_null()` which are special cases of `expect_equal()`
163+
As you write more expectations, you might discover repeated code that you want to extract out in to a helper. For example, testthat has `expect_true()`, `expect_false()`, and `expect_null()` which are special cases of `expect_equal()`.
156164

157165
```{r}
158166
expect_true <- function(object) {
159167
act <- quasi_label(enquo(object))
160-
expect_constant_(act, TRUE, ignore_attr = TRUE)
168+
expect_waldo_equal_("equal", act, TRUE, ignore_attr = TRUE)
161169
}
162170
expect_false <- function(object) {
163171
act <- quasi_label(enquo(object))
164-
expect_constant_(act, FALSE, ignore_attr = TRUE)
172+
expect_waldo_equal_("equal", act, FALSE, ignore_attr = TRUE)
165173
}
166174
expect_null <- function(object, label = NULL) {
167175
act <- quasi_label(enquo(object))
168-
expect_constant_(act, NULL)
176+
expect_waldo_equal_("equal", act, NULL)
169177
}
178+
```
179+
180+
You might wonder why these functions don't call `expect_equal()` directly. Unfortunately creating helper functions is not straightforward in testthat because every `fail()` captures the calling environment in order to give maximally useful tracebacks. Getting this right is not critical (you'll just get a slightly suboptimal traceback in the case of failure) but it's good practice, particularly for testthat itself.
181+
182+
To do things 100% correctly, in your helper function you need to have a `trace_env` argument that defaults to `caller_env()`, and then you need to pass it to every instance of
170183

171-
expect_constant_ <- function(
184+
```{r}
185+
expect_waldo_equal_ <- function(
186+
type,
172187
act,
173-
constant,
174-
ignore_attr = TRUE,
188+
exp,
189+
info,
190+
...,
175191
trace_env = caller_env()
176192
) {
177-
comp <- waldo::compare(
193+
comp <- waldo_compare(
178194
act$val,
179-
constant,
195+
exp$val,
196+
...,
180197
x_arg = "actual",
181-
y_arg = "expected",
182-
ignore_attr = ignore_attr
198+
y_arg = "expected"
183199
)
184200
if (length(comp) != 0) {
185201
msg <- sprintf(
186-
"%s is not %s\n\n%s",
202+
"%s (%s) not %s to %s (%s).\n\n%s",
187203
act$lab,
188-
format(constant),
204+
"`actual`",
205+
type,
206+
exp$lab,
207+
"`expected`",
189208
paste0(comp, collapse = "\n\n")
190209
)
191210
return(fail(msg, info = info, trace_env = trace_env))
192211
}
193-
194212
pass(act$val)
195213
}
214+
196215
```

0 commit comments

Comments
 (0)