Skip to content

Commit e862b8d

Browse files
authored
Improve expect_s3_class() documentation (#2159)
Fixes #2401
1 parent 77ae470 commit e862b8d

File tree

5 files changed

+98
-51
lines changed

5 files changed

+98
-51
lines changed

R/expect-inheritance.R

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@
1818
#' See [expect_vector()] for testing properties of objects created by vctrs.
1919
#'
2020
#' @param type String giving base type (as returned by [typeof()]).
21-
#' @param class Either a character vector of class names, or
22-
#' for `expect_s3_class()` and `expect_s4_class()`, an `NA` to assert
23-
#' that `object` isn't an S3 or S4 object.
21+
#' @param class
22+
#' * `expect_type()`: a single string giving an R base type.
23+
#' * `expect_s3_class()`: a character vector of class names or `NA` to assert
24+
#' that `object` isn't an S3 object. If you provide multiple class names,
25+
#' the test will pass if `object` inherits from any of them, unless
26+
#' `exact = TRUE`.
27+
#' * `expect_s4_class()`: a character vector of class names or `NA` to assert
28+
#' that `object` isn't an S4 object.
29+
#' * `expect_s7_class()`: an [S7::S7_class()] object.
2430
#' @inheritParams expect_that
2531
#' @family expectations
2632
#' @examples
@@ -31,6 +37,15 @@
3137
#' # A data frame is built from a list:
3238
#' expect_type(x, "list")
3339
#'
40+
#' f <- factor(c("a", "b", "c"))
41+
#' o <- ordered(f)
42+
#'
43+
#' # Using multiple class names tests if the object inherits from any of them
44+
#' expect_s3_class(f, c("ordered", "factor"))
45+
#' # Use exact = TRUE to test for exact match
46+
#' show_failure(expect_s3_class(f, c("ordered", "factor"), exact = TRUE))
47+
#' expect_s3_class(o, c("ordered", "factor"), exact = TRUE)
48+
#'
3449
#' # An integer vector is an atomic vector of type "integer"
3550
#' expect_type(x$x, "integer")
3651
#' # It is not an S3 object
@@ -66,8 +81,8 @@ expect_type <- function(object, type) {
6681
#' @export
6782
#' @rdname inheritance-expectations
6883
#' @param exact If `FALSE`, the default, checks that `object` inherits
69-
#' from `class`. If `TRUE`, checks that object has a class that's identical
70-
#' to `class`.
84+
#' from any element of `class`. If `TRUE`, checks that object has a class
85+
#' that's identical to `class`.
7186
expect_s3_class <- function(object, class, exact = FALSE) {
7287
check_bool(exact)
7388

@@ -106,35 +121,6 @@ expect_s3_class <- function(object, class, exact = FALSE) {
106121
pass(act$val)
107122
}
108123

109-
#' @export
110-
#' @rdname inheritance-expectations
111-
expect_s7_class <- function(object, class) {
112-
check_installed("S7")
113-
if (!inherits(class, "S7_class")) {
114-
stop_input_type(class, "an S7 class object")
115-
}
116-
117-
act <- quasi_label(enquo(object))
118-
119-
if (!S7::S7_inherits(object)) {
120-
return(fail(sprintf("%s is not an S7 object", act$lab)))
121-
}
122-
123-
if (!S7::S7_inherits(object, class)) {
124-
obj_class <- setdiff(base::class(object), "S7_object")
125-
class_desc <- paste0("<", obj_class, ">", collapse = "/")
126-
msg <- sprintf(
127-
"%s inherits from %s not <%s>.",
128-
act$lab,
129-
class_desc,
130-
attr(class, "name", TRUE)
131-
)
132-
return(fail(msg))
133-
}
134-
135-
pass(act$val)
136-
}
137-
138124
#' @export
139125
#' @rdname inheritance-expectations
140126
expect_s4_class <- function(object, class) {
@@ -162,13 +148,40 @@ expect_s4_class <- function(object, class) {
162148
}
163149
}
164150
} else {
165-
abort("`class` must be a NA or a character vector")
151+
stop_input_type(class, c("a character vector", "NA"))
166152
}
167153

168154
pass(act$val)
169155
}
170156

171-
isS3 <- function(x) is.object(x) && !isS4(x)
157+
#' @export
158+
#' @rdname inheritance-expectations
159+
expect_s7_class <- function(object, class) {
160+
check_installed("S7")
161+
if (!inherits(class, "S7_class")) {
162+
stop_input_type(class, "an S7 class object")
163+
}
164+
165+
act <- quasi_label(enquo(object))
166+
167+
if (!S7::S7_inherits(object)) {
168+
return(fail(sprintf("%s is not an S7 object", act$lab)))
169+
}
170+
171+
if (!S7::S7_inherits(object, class)) {
172+
obj_class <- setdiff(base::class(object), "S7_object")
173+
class_desc <- paste0("<", obj_class, ">", collapse = "/")
174+
msg <- sprintf(
175+
"%s inherits from %s not <%s>.",
176+
act$lab,
177+
class_desc,
178+
attr(class, "name", TRUE)
179+
)
180+
return(fail(msg))
181+
}
182+
183+
pass(act$val)
184+
}
172185

173186
#' Does an object inherit from a given class?
174187
#'
@@ -177,15 +190,16 @@ isS3 <- function(x) is.object(x) && !isS4(x)
177190
#'
178191
#' `expect_is()` is an older form that uses [inherits()] without checking
179192
#' whether `x` is S3, S4, or neither. Instead, I'd recommend using
180-
#' [expect_type()], [expect_s3_class()] or [expect_s4_class()] to more clearly
181-
#' convey your intent.
193+
#' [expect_type()], [expect_s3_class()], or [expect_s4_class()] to more clearly
194+
#' convey your intent.
182195
#'
183196
#' @section 3rd edition:
184197
#' `r lifecycle::badge("deprecated")`
185198
#'
186199
#' `expect_is()` is formally deprecated in the 3rd edition.
187200
#'
188201
#' @keywords internal
202+
#' @param class Class name passed to `inherits()`.
189203
#' @inheritParams expect_type
190204
#' @export
191205
expect_is <- function(object, class, info = NULL, label = NULL) {
@@ -212,6 +226,9 @@ expect_is <- function(object, class, info = NULL, label = NULL) {
212226
pass(act$val)
213227
}
214228

229+
# Helpers ----------------------------------------------------------------------
230+
231+
isS3 <- function(x) is.object(x) && !isS4(x)
215232

216233
format_class <- function(x) {
217234
paste0(encodeString(x, quote = "'"), collapse = "/")

man/expect_is.Rd

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

man/inheritance-expectations.Rd

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

tests/testthat/_snaps/expect-inheritance.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@
3131
Error in `expect_s3_class()`:
3232
! `exact` must be `TRUE` or `FALSE`, not the string "yes".
3333

34+
---
35+
36+
Code
37+
expect_s4_class(factor("a"), 1)
38+
Condition
39+
Error in `expect_s4_class()`:
40+
! `class` must be a character vector or NA, not the number 1.
41+
3442
# test_s3_class respects class hierarchy
3543

3644
`x` inherits from 'a'/'b' not 'c'.

tests/testthat/test-expect-inheritance.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ test_that("expect_s4_class allows unquoting of first argument", {
8181
expect_success(expect_s4_class(!!rlang::quo(obj), "new_class"))
8282
})
8383

84+
85+
test_that("expect_s3_class validates its inputs", {
86+
expect_snapshot(error = TRUE, {
87+
expect_s4_class(factor("a"), 1)
88+
})
89+
})
90+
91+
8492
# expect_s7_class --------------------------------------------------------
8593

8694
test_that("can check with actual class", {

0 commit comments

Comments
 (0)