Skip to content

Commit dba0ef1

Browse files
authored
Improved composition (#2250)
Rework expectations to call `pass()` and `fail()` purely for their side-effects, and then ensure that every expectation returns the first argument invisibly. Fixes #2246
1 parent 6e3c1e5 commit dba0ef1

39 files changed

+505
-422
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# testthat (development version)
22

3+
* Expectations now consistently return the value of the first argument, regardless of whether the expectation succeeds or fails. The primary exception are `expect_message()` and friends which will return the condition. This shouldn't affect existing tests, but will make failures clearer when you chain together multiple expectations (#2246).
34
* `set_state_inspector()` gains `tolerance` argument and ignores minor FP differences by default (@mcol, #2237).
45
* `expect_vector()` fails, instead of erroring, if `object` is not a vector (@plietar, #2224).
56
* New `vignette("mocking")` explains mocking in detail (#1265).

R/expect-comparison.R

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,60 @@ expect_compare_ <- function(
2828
operator <- match.arg(operator)
2929
op <- match.fun(operator)
3030

31-
actual_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
32-
3331
cmp <- op(act$val, exp$val)
3432
if (length(cmp) != 1 || !is.logical(cmp)) {
3533
cli::cli_abort(
36-
"Result of comparison must be a single logical value.",
34+
"Result of comparison must be `TRUE`, `FALSE`, or `NA`",
3735
call = trace_env
3836
)
37+
} else if (!isTRUE(cmp)) {
38+
msg <- failure_compare(act, exp, operator)
39+
fail(msg, trace_env = trace_env)
40+
} else {
41+
pass()
3942
}
40-
if (!isTRUE(cmp)) {
41-
diff <- act$val - exp$val
42-
msg_exp <- sprintf("Expected %s %s %s.", act$lab, operator, exp$lab)
43-
44-
digits <- max(
45-
digits(act$val),
46-
digits(exp$val),
47-
min_digits(act$val, exp$val)
48-
)
43+
}
4944

50-
msg_act <- sprintf(
51-
"Actual comparison: %s %s %s",
52-
num_exact(act$val, digits),
53-
actual_op,
54-
num_exact(exp$val, digits)
55-
)
45+
failure_compare <- function(act, exp, operator) {
46+
actual_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
5647

57-
if (is.na(diff)) {
58-
msg_diff <- NULL
59-
} else {
60-
msg_diff <- sprintf(
61-
"Difference: %s %s 0",
62-
num_exact(diff, digits),
63-
actual_op
64-
)
65-
}
66-
return(fail(c(msg_exp, msg_act, msg_diff), trace_env = trace_env))
48+
diff <- act$val - exp$val
49+
msg_exp <- sprintf("Expected %s %s %s.", act$lab, operator, exp$lab)
50+
51+
digits <- max(
52+
digits(act$val),
53+
digits(exp$val),
54+
min_digits(act$val, exp$val)
55+
)
56+
57+
msg_act <- sprintf(
58+
"Actual comparison: %s %s %s",
59+
num_exact(act$val, digits),
60+
actual_op,
61+
num_exact(exp$val, digits)
62+
)
63+
64+
if (is.na(diff)) {
65+
msg_diff <- NULL
66+
} else {
67+
msg_diff <- sprintf(
68+
"Difference: %s %s 0",
69+
num_exact(diff, digits),
70+
actual_op
71+
)
6772
}
68-
pass(act$val)
73+
74+
c(msg_exp, msg_act, msg_diff)
6975
}
76+
7077
#' @export
7178
#' @rdname comparison-expectations
7279
expect_lt <- function(object, expected, label = NULL, expected.label = NULL) {
7380
act <- quasi_label(enquo(object), label)
7481
exp <- quasi_label(enquo(expected), expected.label)
7582

7683
expect_compare_("<", act, exp)
84+
invisible(act$val)
7785
}
7886

7987
#' @export
@@ -83,6 +91,7 @@ expect_lte <- function(object, expected, label = NULL, expected.label = NULL) {
8391
exp <- quasi_label(enquo(expected), expected.label)
8492

8593
expect_compare_("<=", act, exp)
94+
invisible(act$val)
8695
}
8796

8897
#' @export
@@ -92,6 +101,7 @@ expect_gt <- function(object, expected, label = NULL, expected.label = NULL) {
92101
exp <- quasi_label(enquo(expected), expected.label)
93102

94103
expect_compare_(">", act, exp)
104+
invisible(act$val)
95105
}
96106

97107
#' @export
@@ -101,6 +111,7 @@ expect_gte <- function(object, expected, label = NULL, expected.label = NULL) {
101111
exp <- quasi_label(enquo(expected), expected.label)
102112

103113
expect_compare_(">=", act, exp)
114+
invisible(act$val)
104115
}
105116

106117

R/expect-condition.R

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,11 @@ expect_error <- function(
143143
# Access error fields with `[[` rather than `$` because the
144144
# `$.Throwable` from the rJava package throws with unknown fields
145145
if (!is.null(msg)) {
146-
return(fail(msg, info = info, trace = act$cap[["trace"]]))
146+
fail(msg, info = info, trace = act$cap[["trace"]])
147+
} else {
148+
pass()
147149
}
148-
pass(act$val %||% act$cap)
150+
invisible(act$val %||% act$cap)
149151
}
150152
}
151153

@@ -198,9 +200,11 @@ expect_warning <- function(
198200
cond_type = "warnings"
199201
)
200202
if (!is.null(msg)) {
201-
return(fail(msg, info = info))
203+
fail(msg, info = info)
204+
} else {
205+
pass()
202206
}
203-
pass(act$val)
207+
invisible(act$val)
204208
}
205209
}
206210

@@ -236,9 +240,11 @@ expect_message <- function(
236240
act <- quasi_capture(enquo(object), label, capture_messages)
237241
msg <- compare_messages(act$cap, act$lab, regexp = regexp, all = all, ...)
238242
if (!is.null(msg)) {
239-
return(fail(msg, info = info))
243+
fail(msg, info = info)
244+
} else {
245+
pass()
240246
}
241-
pass(act$val)
247+
invisible(act$val)
242248
}
243249
}
244250

@@ -285,9 +291,11 @@ expect_condition <- function(
285291
cond_type = "condition"
286292
)
287293
if (!is.null(msg)) {
288-
return(fail(msg, info = info, trace = act$cap[["trace"]]))
294+
fail(msg, info = info, trace = act$cap[["trace"]])
295+
} else {
296+
pass()
289297
}
290-
pass(act$val %||% act$cap)
298+
invisible(act$val %||% act$cap)
291299
}
292300
}
293301

@@ -327,16 +335,18 @@ expect_condition_matching_ <- function(
327335
# Access error fields with `[[` rather than `$` because the
328336
# `$.Throwable` from the rJava package throws with unknown fields
329337
if (!is.null(msg)) {
330-
return(fail(
338+
fail(
331339
msg,
332340
info = info,
333341
trace = act$cap[["trace"]],
334342
trace_env = trace_env
335-
))
343+
)
344+
} else {
345+
pass()
336346
}
337347
# If a condition was expected, return it. Otherwise return the value
338348
# of the expression.
339-
pass(if (expected) act$cap else act$val)
349+
invisible(if (expected) act$cap else act$val)
340350
}
341351

342352
# -------------------------------------------------------------------------

R/expect-constant.R

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ NULL
3131
expect_true <- function(object, info = NULL, label = NULL) {
3232
act <- quasi_label(enquo(object), label)
3333
exp <- labelled_value(TRUE, "TRUE")
34+
3435
expect_waldo_constant_(act, exp, info = info, ignore_attr = TRUE)
36+
invisible(act$val)
3537
}
3638

3739
#' @export
3840
#' @rdname logical-expectations
3941
expect_false <- function(object, info = NULL, label = NULL) {
4042
act <- quasi_label(enquo(object), label)
4143
exp <- labelled_value(FALSE, "FALSE")
44+
4245
expect_waldo_constant_(act, exp, info = info, ignore_attr = TRUE)
46+
invisible(act$val)
4347
}
4448

4549
#' Do you expect `NULL`?
@@ -59,7 +63,9 @@ expect_false <- function(object, info = NULL, label = NULL) {
5963
expect_null <- function(object, info = NULL, label = NULL) {
6064
act <- quasi_label(enquo(object), label)
6165
exp <- labelled_value(NULL, "NULL")
66+
6267
expect_waldo_constant_(act, exp, info = info)
68+
invisible(act$val)
6369
}
6470

6571
expect_waldo_constant_ <- function(
@@ -82,7 +88,8 @@ expect_waldo_constant_ <- function(
8288
"Differences:",
8389
paste0(comp, collpase = "\n")
8490
)
85-
return(fail(msg, info = info, trace_env = trace_env))
91+
fail(msg, info = info, trace_env = trace_env)
92+
} else {
93+
pass()
8694
}
87-
pass(act$val)
8895
}

R/expect-equality.R

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,18 @@ expect_equal <- function(
7676
comp <- compare(act$val, exp$val, ...)
7777
}
7878

79-
if (!comp$equal) {
79+
if (comp$equal) {
80+
pass()
81+
} else {
8082
msg <- c(
8183
sprintf("Expected %s to equal %s.", act$lab, exp$lab),
8284
"Differences:",
8385
comp$message
8486
)
85-
return(fail(msg, info = info))
87+
fail(msg, info = info)
8688
}
87-
pass(act$val)
8889
}
90+
invisible(act$val)
8991
}
9092

9193

@@ -105,28 +107,25 @@ expect_identical <- function(
105107
if (edition_get() >= 3) {
106108
expect_waldo_equal_("identical", act, exp, info, ...)
107109
} else {
108-
ident <- identical(act$val, exp$val, ...)
109-
if (ident) {
110-
msg_act <- NULL
110+
if (identical(act$val, exp$val, ...)) {
111+
pass()
111112
} else {
112113
compare <- compare(act$val, exp$val)
113114
if (compare$equal) {
114115
msg_act <- "Objects equal but not identical"
115116
} else {
116117
msg_act <- compare$message
117118
}
118-
}
119119

120-
if (!ident) {
121120
msg <- c(
122121
sprintf("Expected %s to be identical to %s.", act$lab, exp$lab),
123122
"Differences:",
124123
msg_act
125124
)
126-
return(fail(msg, info = info))
125+
fail(msg, info = info)
127126
}
128-
pass(act$val)
129127
}
128+
invisible(act$val)
130129
}
131130

132131
expect_waldo_equal_ <- function(
@@ -144,15 +143,16 @@ expect_waldo_equal_ <- function(
144143
x_arg = "actual",
145144
y_arg = "expected"
146145
)
147-
if (length(comp) != 0) {
146+
if (length(comp) == 0) {
147+
pass()
148+
} else {
148149
msg <- c(
149150
sprintf("Expected %s to be %s to %s.", act$lab, type, exp$lab),
150151
"Differences:",
151152
paste0(comp, collpase = "\n")
152153
)
153-
return(fail(msg, info = info, trace_env = trace_env))
154+
fail(msg, info = info, trace_env = trace_env)
154155
}
155-
pass(act$val)
156156
}
157157

158158
#' Is an object equal to the expected value, ignoring attributes?
@@ -203,7 +203,9 @@ expect_equivalent <- function(
203203
exp$lab,
204204
comp$message
205205
)
206-
return(fail(msg, info = info))
206+
fail(msg, info = info)
207+
} else {
208+
pass()
207209
}
208-
pass(act$val)
210+
invisible(act$val)
209211
}

0 commit comments

Comments
 (0)