You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 🙂.
43
43
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.
45
45
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:
47
49
48
50
```{r}
49
51
mtcars |>
@@ -52,15 +54,15 @@ mtcars |>
52
54
expect_length(11)
53
55
```
54
56
55
-
## Testing your expectations
57
+
###Testing your expectations
56
58
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:
58
60
59
61
*`expect_success()` checks that your expectation emits exactly one success and zero failures.
60
62
*`expect_failure()` checks that your expectation emits exactly one failure and zero successes.
61
63
*`expect_failure_snapshot()` captures the failure message in a snapshot, making it easier to review if it's useful or not.
62
64
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.
The following sections show you a few more variations, losely based on existing testthat expectations.
83
+
80
84
### `expect_vector_length()`
81
85
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:
To make your failure messages as actionable as possible, state both what the object is and what you expected:
104
-
105
113
```{r}
106
114
#| error: true
107
-
expect_vector_length(mean, 10)
115
+
expect_vector_length(mean, 1)
108
116
expect_vector_length(mtcars, 15)
109
117
```
110
118
111
119
### `expect_s3_class()`
112
120
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:
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()`.
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
170
183
171
-
expect_constant_ <- function(
184
+
```{r}
185
+
expect_waldo_equal_ <- function(
186
+
type,
172
187
act,
173
-
constant,
174
-
ignore_attr = TRUE,
188
+
exp,
189
+
info,
190
+
...,
175
191
trace_env = caller_env()
176
192
) {
177
-
comp <- waldo::compare(
193
+
comp <- waldo_compare(
178
194
act$val,
179
-
constant,
195
+
exp$val,
196
+
...,
180
197
x_arg = "actual",
181
-
y_arg = "expected",
182
-
ignore_attr = ignore_attr
198
+
y_arg = "expected"
183
199
)
184
200
if (length(comp) != 0) {
185
201
msg <- sprintf(
186
-
"%s is not %s\n\n%s",
202
+
"%s (%s) not %s to %s (%s).\n\n%s",
187
203
act$lab,
188
-
format(constant),
204
+
"`actual`",
205
+
type,
206
+
exp$lab,
207
+
"`expected`",
189
208
paste0(comp, collapse = "\n\n")
190
209
)
191
210
return(fail(msg, info = info, trace_env = trace_env))
0 commit comments