Skip to content

Commit cc79388

Browse files
authored
Improve self-testing capabilities (#2119)
`expect_success()` and `expect_failure()` are now stricter, testing the full set of assumptions (i.e. exactly one success/failure and zero failures/successes). This means that `expect_no_successes()` and `expect_no_failures()` can be deprecated. Fixes #2110
1 parent 1988ac8 commit cc79388

File tree

12 files changed

+249
-105
lines changed

12 files changed

+249
-105
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+
* `expect_no_failures()` and `expect_no_successes()` are now deprecated as `expect_success()` now test for no failures and `expect_failure()` tests for no successes (#)
34
* New `pass()` function to use in place of `succeed()` (#2113).
45
* `expectation()` is now a combination of `new_expectation()` and `exp_signal()` (#2125).
56
* `is_null()`/`matches()` deprecated in 2.0.0 (2017-12-19) and `is_true()`/`is_false()` deprecated in 2.1.0 (2019-04-23) have been removed (#2109).

R/expect-self-test.R

Lines changed: 90 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
1-
capture_failure <- new_capture("expectation_failure")
2-
capture_success <- function(expr) {
1+
capture_success_failure <- function(expr) {
32
cnd <- NULL
43

5-
withCallingHandlers(
6-
expr,
7-
expectation_failure = function(cnd) {
8-
invokeRestart("continue_test")
9-
},
10-
expectation_success = function(cnd) {
11-
cnd <<- cnd
12-
}
4+
n_success <- 0
5+
n_failure <- 0
6+
7+
last_failure <- NULL
8+
9+
withRestarts(
10+
withCallingHandlers(
11+
expr,
12+
expectation_failure = function(cnd) {
13+
last_failure <<- cnd
14+
n_failure <<- n_failure + 1
15+
# Finish the test without bubbling up
16+
invokeRestart("failed")
17+
},
18+
expectation_success = function(cnd) {
19+
n_success <<- n_success + 1
20+
# Don't bubble up to any other handlers
21+
invokeRestart("continue_test")
22+
}
23+
),
24+
failed = function() {}
1325
)
14-
cnd
15-
}
1626

17-
new_capture("expectation_success")
27+
list(
28+
n_success = n_success,
29+
n_failure = n_failure,
30+
last_failure = last_failure
31+
)
32+
}
1833

1934
#' Tools for testing expectations
2035
#'
2136
#' @description
22-
#' * `expect_success()` and `expect_failure()` check that there's at least
23-
#' one success or failure respectively.
24-
#' * `expect_snapshot_failure()` records the failure message so that you can
25-
#' manually check that it is informative.
26-
#' * `expect_no_success()` and `expect_no_failure()` check that are no
27-
#' successes or failures.
37+
#' `expect_success()` checks that there's exactly one success and no failures;
38+
#' `expect_failure()` checks that there's exactly one failure and no successes.
39+
#' `expect_snapshot_failure()` records the failure message so that you can
40+
#' manually check that it is informative.
2841
#'
2942
#' Use `show_failure()` in examples to print the failure message without
3043
#' throwing an error.
@@ -34,42 +47,50 @@ new_capture("expectation_success")
3447
#' @param ... Other arguments passed on to [expect_match()].
3548
#' @export
3649
expect_success <- function(expr) {
37-
exp <- capture_success(expr)
50+
status <- capture_success_failure(expr)
3851

39-
if (is.null(exp)) {
52+
if (status$n_success == 0) {
4053
fail("Expectation did not succeed")
41-
} else {
42-
succeed()
54+
} else if (status$n_success > 1) {
55+
fail(sprintf(
56+
"Expectation succeeded %i times, instead of once",
57+
status$n_success
58+
))
4359
}
44-
invisible(NULL)
45-
}
46-
47-
#' @export
48-
#' @rdname expect_success
49-
expect_no_success <- function(expr) {
50-
exp <- capture_success(expr)
5160

52-
if (!is.null(exp)) {
53-
fail("Expectation succeeded")
54-
} else {
55-
succeed()
61+
if (status$n_failure > 0) {
62+
fail(sprintf(
63+
"Expectation failed %i times, instead of zero",
64+
status$n_failure
65+
))
5666
}
57-
invisible(NULL)
67+
68+
pass(NULL)
5869
}
5970

6071
#' @export
6172
#' @rdname expect_success
6273
expect_failure <- function(expr, message = NULL, ...) {
63-
exp <- capture_failure(expr)
74+
status <- capture_success_failure(expr)
6475

65-
if (is.null(exp)) {
76+
if (status$n_failure == 0) {
6677
fail("Expectation did not fail")
67-
} else if (!is.null(message)) {
68-
expect_match(exp$message, message, ...)
69-
} else {
70-
succeed()
78+
} else if (status$n_failure > 1) {
79+
# This should be impossible, but including for completeness
80+
fail("Expectation failed more than once")
7181
}
72-
invisible(NULL)
82+
83+
if (status$n_success != 0) {
84+
fail(sprintf(
85+
"Expectation succeeded %i times, instead of never",
86+
status$n_success
87+
))
88+
}
89+
90+
if (!is.null(message)) {
91+
return(expect_match(status$last_failure$message, message, ...))
92+
}
93+
pass(NULL)
7394
}
7495

7596
#' @export
@@ -78,12 +99,36 @@ expect_snapshot_failure <- function(expr) {
7899
expect_snapshot_error(expr, "expectation_failure")
79100
}
80101

102+
#' Test for absence of success or failure
103+
#'
104+
#' @description
105+
#' `r lifecycle::badge("deprecated")`
106+
#'
107+
#' These functions are deprecated because [expect_success()] and
108+
#' [expect_failure()] now test for exactly one success or no failures, and
109+
#' exactly one failure and no successes.
110+
#'
111+
#' @keywords internal
81112
#' @export
82-
#' @rdname expect_success
113+
expect_no_success <- function(expr) {
114+
lifecycle::deprecate_warn("3.3.0", "expect_no_success()", "expect_failure()")
115+
status <- capture_success_failure(expr)
116+
117+
if (status$n_success > 0) {
118+
fail("Expectation succeeded")
119+
} else {
120+
succeed()
121+
}
122+
invisible(NULL)
123+
}
124+
125+
#' @export
126+
#' @rdname expect_no_success
83127
expect_no_failure <- function(expr) {
84-
exp <- capture_failure(expr)
128+
lifecycle::deprecate_warn("3.3.0", "expect_no_failure()", "expect_success()")
129+
status <- capture_success_failure(expr)
85130

86-
if (!is.null(exp)) {
131+
if (status$n_failure > 0) {
87132
fail("Expectation failed")
88133
} else {
89134
succeed()

R/try-again.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ try_again <- function(times, code) {
4444
times <- times - 1L
4545
}
4646

47-
stop(e)
47+
exp_signal(e)
4848
}

man/expect_no_success.Rd

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/expect_success.Rd

Lines changed: 3 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# errors in expect_success bubble up
2+
3+
Code
4+
expect_success(abort("error"))
5+
Condition
6+
Error:
7+
! error
8+
9+
# expect_no are deprecated
10+
11+
Code
12+
expect_no_failure(succeed())
13+
Condition
14+
Warning:
15+
`expect_no_failure()` was deprecated in testthat 3.3.0.
16+
i Please use `expect_success()` instead.
17+
Code
18+
expect_no_success(fail())
19+
Condition
20+
Warning:
21+
`expect_no_success()` was deprecated in testthat 3.3.0.
22+
i Please use `expect_failure()` instead.
23+

tests/testthat/test-expect-condition.R

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,17 @@ test_that("can capture Throwable conditions from rJava", {
7878

7979
test_that("capture correct trace_env (#1994)", {
8080
# This should fail, not error
81-
expect_failure(expect_error(stop("oops")) |> expect_warning())
82-
expect_failure(expect_warning(expect_error(stop("oops"))))
81+
status <- capture_success_failure({
82+
stop("oops") |> expect_error() |> expect_warning()
83+
})
84+
expect_equal(status$n_success, 1) # from expect_error()
85+
expect_equal(status$n_failure, 1) # from expect_warning()
86+
87+
status <- capture_success_failure({
88+
stop("oops") %>% expect_error() %>% expect_warning()
89+
})
90+
expect_equal(status$n_success, 1) # from expect_error()
91+
expect_equal(status$n_failure, 1) # from expect_warning()
8392
})
8493

8594
# expect_warning() ----------------------------------------------------------

tests/testthat/test-expect-known.R

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ test_that("uses specified width", {
1212

1313
test_that("creates file on first run", {
1414
file <- withr::local_tempfile()
15-
expect_success(
16-
expect_warning(
17-
expect_known_output(cat("ok!\n"), file),
18-
"Creating reference"
19-
)
20-
)
15+
expect_known_output(cat("ok!\n"), file) |>
16+
expect_success() |>
17+
expect_warning("Creating reference")
2118

2219
expect_true(file.exists(file))
2320
})
@@ -76,12 +73,9 @@ test_that("correctly matches to a file", {
7673
})
7774

7875
test_that("first run is successful", {
79-
expect_success(
80-
expect_warning(
81-
expect_known_value(2, "two.rds"),
82-
"Creating reference"
83-
)
84-
)
76+
expect_known_value(2, "two.rds") |>
77+
expect_success() |>
78+
expect_warning("Creating reference")
8579
unlink("two.rds")
8680
})
8781

@@ -130,12 +124,9 @@ test_that("version 3 is possible", {
130124
# expect_known_hash -------------------------------------------------------
131125

132126
test_that("empty hash succeeds with warning", {
133-
expect_success(
134-
expect_warning(
135-
expect_known_hash(1:10),
136-
"No recorded hash"
137-
)
138-
)
127+
expect_known_hash(1:10) |>
128+
expect_success() |>
129+
expect_warning("No recorded hash")
139130
})
140131

141132
test_that("only succeeds if hash is correct", {

tests/testthat/test-expect-no-condition.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ test_that("expect_no_* pass with pure code", {
2020
})
2121

2222
test_that("expect_no_* don't emit success when they fail", {
23-
expect_no_success(expect_no_error(stop("!")))
23+
expect_failure(expect_no_error(stop("!")))
2424
})
2525

2626
test_that("capture correct trace_env (#1994)", {

0 commit comments

Comments
 (0)