Skip to content

Commit db3670a

Browse files
Add more informative feedback in expect_named() (#2130)
Fixes #2091. Fixes #1558.
1 parent 2e911f4 commit db3670a

File tree

6 files changed

+115
-20
lines changed

6 files changed

+115
-20
lines changed

NEWS.md

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

3+
* `expect_named()` now gives more informative errors (#2091).
4+
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
35
* `test_that()` no longer warns about the absence of `{}` since it no longer seems to be necessary.
46
* `test_that()`, `describe()`, and `it()` can now be arbitrarily nested. Each component will skip only if it and its subtests don't contain any expectations. The interactive stop reporter has been fixed so it doesn't duplicate failures. (#2063, #2188).
57
* Test filtering now works with `it()`, and the `desc` argument can take a character vector in order to recursively filter subtests (i.e. `it()` nested inside of `describe()`) (#2118).

R/expect-equality.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ expect_waldo_equal_ <- function(
126126
exp,
127127
info = NULL,
128128
...,
129-
trace_env = caller_env()
129+
trace_env = caller_env(),
130+
error_prefix = NULL
130131
) {
131132
comp <- waldo_compare(
132133
act$val,
@@ -145,6 +146,7 @@ expect_waldo_equal_ <- function(
145146
"`expected`",
146147
paste0(comp, collapse = "\n\n")
147148
)
149+
msg <- paste0(error_prefix, msg)
148150
return(fail(msg, info = info, trace_env = trace_env))
149151
}
150152
pass(act$val)

R/expect-named.R

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,24 @@ expect_named <- function(
3636
check_bool(ignore.case)
3737

3838
act <- quasi_label(enquo(object), label)
39-
act$names <- names(act$val)
4039

4140
if (missing(expected)) {
42-
if (identical(act$names, NULL)) {
43-
msg <- sprintf("%s does not have names.", act$lab)
44-
return(fail(msg))
45-
}
46-
} else {
47-
exp_names <- normalise_names(expected, ignore.order, ignore.case)
48-
act$names <- normalise_names(act$names, ignore.order, ignore.case)
41+
return(expect_has_names_(act))
42+
}
43+
44+
exp <- quasi_label(enquo(expected), arg = "expected")
4945

50-
if (!identical(act$names, exp_names)) {
51-
msg <- sprintf(
52-
"Names of %s (%s) don't match %s",
53-
act$lab,
54-
paste0("'", act$names, "'", collapse = ", "),
55-
paste0("'", exp_names, "'", collapse = ", ")
56-
)
57-
return(fail(msg, info = info))
58-
}
46+
exp$val <- normalise_names(exp$val, ignore.order, ignore.case)
47+
act_names <- normalise_names(names(act$val), ignore.order, ignore.case)
48+
49+
if (ignore.order) {
50+
act <- labelled_value(act_names, act$lab)
51+
return(expect_setequal_(act, exp, error_prefix = "Names of "))
52+
} else {
53+
act <- labelled_value(act_names, act$lab)
54+
return(expect_waldo_equal_("equal", act, exp, error_prefix = "Names of "))
5955
}
56+
6057
pass(act$val)
6158
}
6259

@@ -74,3 +71,12 @@ normalise_names <- function(x, ignore.order = FALSE, ignore.case = FALSE) {
7471

7572
x
7673
}
74+
75+
expect_has_names_ <- function(act, trace_env = caller_env()) {
76+
act_names <- names(act$val)
77+
if (identical(act_names, NULL)) {
78+
msg <- sprintf("%s does not have names.", act$lab)
79+
return(fail(msg, trace_env = trace_env))
80+
}
81+
return(pass(act$val))
82+
}

R/expect-setequal.R

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,23 @@ expect_setequal <- function(object, expected) {
3434
testthat_warn("expect_setequal() ignores names")
3535
}
3636

37+
expect_setequal_(act, exp)
38+
}
39+
40+
expect_setequal_ <- function(
41+
act,
42+
exp,
43+
trace_env = caller_env(),
44+
error_prefix = NULL
45+
) {
3746
act_miss <- unique(act$val[!act$val %in% exp$val])
3847
exp_miss <- unique(exp$val[!exp$val %in% act$val])
3948

4049
if (length(exp_miss) || length(act_miss)) {
41-
return(fail(paste0(
50+
msg <- paste0(
51+
if (!is.null(error_prefix)) {
52+
error_prefix
53+
},
4254
act$lab,
4355
" (`actual`) and ",
4456
exp$lab,
@@ -49,7 +61,8 @@ expect_setequal <- function(object, expected) {
4961
if (length(exp_miss)) {
5062
paste0("* Only in `expected`: ", values(exp_miss), "\n")
5163
}
52-
)))
64+
)
65+
return(fail(msg, trace_env = trace_env))
5366
}
5467
pass(act$val)
5568
}

tests/testthat/_snaps/expect-named.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
# provide useful feedback on failure
2+
3+
Names of c(a = 1) (`actual`) and c("a", "b") (`expected`) don't have the same values.
4+
* Only in `expected`: "b"
5+
6+
7+
---
8+
9+
Names of c(a = 1, b = 1) (`actual`) and c("a") (`expected`) don't have the same values.
10+
* Only in `actual`: "b"
11+
12+
13+
---
14+
15+
Names of c(a = 1) (`actual`) and c("b") (`expected`) don't have the same values.
16+
* Only in `actual`: "a"
17+
* Only in `expected`: "b"
18+
19+
20+
---
21+
22+
Names of c(a = 1) (`actual`) is not equal to c("a", "b") (`expected`).
23+
24+
`actual`: "a"
25+
`expected`: "a" "b"
26+
27+
---
28+
29+
Names of c(a = 1, b = 1) (`actual`) is not equal to c("a") (`expected`).
30+
31+
`actual`: "a" "b"
32+
`expected`: "a"
33+
34+
---
35+
36+
Names of c(a = 1) (`actual`) is not equal to c("b") (`expected`).
37+
38+
`actual`: "a"
39+
`expected`: "b"
40+
141
# expect_named validates its inputs
242

343
Code

tests/testthat/test-expect-named.R

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,41 @@ test_that("expected_named optionally ignores order", {
2020
))
2121
})
2222

23+
test_that("provide useful feedback on failure", {
24+
expect_snapshot_error(
25+
expect_named(c(a = 1), c("a", "b"), ignore.order = TRUE)
26+
)
27+
expect_snapshot_error(
28+
expect_named(c(a = 1, b = 1), c("a"), ignore.order = TRUE)
29+
)
30+
expect_snapshot_error(
31+
expect_named(c(a = 1), c("b"), ignore.order = TRUE)
32+
)
33+
34+
expect_snapshot_error(
35+
expect_named(c(a = 1), c("a", "b"), ignore.order = FALSE)
36+
)
37+
expect_snapshot_error(
38+
expect_named(c(a = 1, b = 1), c("a"), ignore.order = FALSE)
39+
)
40+
expect_snapshot_error(
41+
expect_named(c(a = 1), c("b"), ignore.order = FALSE)
42+
)
43+
})
44+
2345
test_that("expect_named validates its inputs", {
2446
expect_snapshot(error = TRUE, {
2547
expect_named(c(a = 1), "a", ignore.order = "yes")
2648
expect_named(c(a = 1), "a", ignore.case = "yes")
2749
})
2850
})
51+
52+
test_that("expect_named accepts glue for 'expected'", {
53+
n <- structure(
54+
c("v1", "v2", "v3", "v4", "v5"),
55+
class = c("glue", "character")
56+
)
57+
v <- set_names(1:5, n)
58+
59+
expect_named(v, n)
60+
})

0 commit comments

Comments
 (0)