diff --git a/DESCRIPTION b/DESCRIPTION index e3b13ebf4..4b5482089 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,6 +43,7 @@ Suggests: knitr, rmarkdown, rstudioapi, + S7, shiny, usethis, vctrs (>= 0.1.0), diff --git a/NAMESPACE b/NAMESPACE index deb5f2010..8f0286c3b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -117,6 +117,7 @@ export(expect_output_file) export(expect_reference) export(expect_s3_class) export(expect_s4_class) +export(expect_s7_class) export(expect_setequal) export(expect_silent) export(expect_snapshot) diff --git a/NEWS.md b/NEWS.md index c9028b2b7..44e957b05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # testthat (development version) +* New `expect_s7_class()` for testing if an object is an S7 class (#1580). * `expect_error()` and friends now error if you supply `...` but not `pattern` (#1932). * New `expect_no_failure()`, `expect_no_success()` and `expect_snapshot_failure()` provide more options for testing expectations. * `expect_error()` and friends no longer give an uninformative error if they fail inside a magrittr pipe (#1994). diff --git a/R/expect-inheritance.R b/R/expect-inheritance.R index 2280e89cc..10677a556 100644 --- a/R/expect-inheritance.R +++ b/R/expect-inheritance.R @@ -12,6 +12,8 @@ #' * `expect_s4_class(x, class)` checks that `x` is an S4 object that #' [is()] `class`. #' * `expect_s4_class(x, NA)` checks that `x` isn't an S4 object. +#' * `expect_s7_class(x, Class)` checks that `x` is an S7 object that +#' [S7::S7_inherits()] from `Class` #' #' See [expect_vector()] for testing properties of objects created by vctrs. #' @@ -92,6 +94,33 @@ expect_s3_class <- function(object, class, exact = FALSE) { invisible(act$val) } +#' @export +#' @rdname inheritance-expectations +expect_s7_class <- function(object, class) { + check_installed("S7") + if (!inherits(class, "S7_class")) { + stop_input_type(class, "an S7 class object") + } + + act <- quasi_label(enquo(object), arg = "object") + + if (!S7::S7_inherits(object)) { + fail(sprintf("%s is not an S7 object", act$lab)) + } else { + expect( + S7::S7_inherits(object, class), + sprintf( + "%s inherits from %s not <%s>.", + act$lab, + paste0("<", setdiff(base::class(object), "S7_object"), ">", collapse = "/"), + attr(class, "name", TRUE) + ) + ) + } + + invisible(act$val) +} + #' @export #' @rdname inheritance-expectations expect_s4_class <- function(object, class) { diff --git a/man/inheritance-expectations.Rd b/man/inheritance-expectations.Rd index 1b2191620..e43b0b89e 100644 --- a/man/inheritance-expectations.Rd +++ b/man/inheritance-expectations.Rd @@ -4,6 +4,7 @@ \alias{inheritance-expectations} \alias{expect_type} \alias{expect_s3_class} +\alias{expect_s7_class} \alias{expect_s4_class} \title{Does code return an object inheriting from the expected base type, S3 class, or S4 class?} @@ -12,6 +13,8 @@ expect_type(object, type) expect_s3_class(object, class, exact = FALSE) +expect_s7_class(object, class) + expect_s4_class(object, class) } \arguments{ @@ -41,6 +44,8 @@ the vocabulary used here. \item \code{expect_s4_class(x, class)} checks that \code{x} is an S4 object that \code{\link[=is]{is()}} \code{class}. \item \code{expect_s4_class(x, NA)} checks that \code{x} isn't an S4 object. +\item \code{expect_s7_class(x, Class)} checks that \code{x} is an S7 object that +\code{\link[S7:S7_inherits]{S7::S7_inherits()}} from \code{Class} } See \code{\link[=expect_vector]{expect_vector()}} for testing properties of objects created by vctrs. diff --git a/tests/testthat/_snaps/expect-inheritance.md b/tests/testthat/_snaps/expect-inheritance.md index 6bcdd9f68..44ae80a40 100644 --- a/tests/testthat/_snaps/expect-inheritance.md +++ b/tests/testthat/_snaps/expect-inheritance.md @@ -18,3 +18,19 @@ `x` inherits from 'a'/'b' not 'c'/'d'. +# checks its inputs + + Code + expect_s7_class(1, 1) + Condition + Error in `expect_s7_class()`: + ! `class` must be an S7 class object, not the number 1. + +# can check with actual class + + Foo() inherits from not . + +--- + + Baz() inherits from / not . + diff --git a/tests/testthat/test-expect-inheritance.R b/tests/testthat/test-expect-inheritance.R index 0129dc090..721b847b0 100644 --- a/tests/testthat/test-expect-inheritance.R +++ b/tests/testthat/test-expect-inheritance.R @@ -56,8 +56,24 @@ test_that("test_s3_class can request exact match", { expect_success(expect_s3_class(x, c("a", "b"), exact = TRUE)) }) - test_that("expect_s3_class allows unquoting of first argument", { f <- factor("a") expect_success(expect_s3_class(!! rlang::quo(f), "factor")) }) + + +# expect_s7_class -------------------------------------------------------- + +test_that("checks its inputs", { + expect_snapshot(expect_s7_class(1, 1), error = TRUE) +}) + +test_that("can check with actual class", { + Foo <- S7::new_class("Foo") + Bar <- S7::new_class("Bar") + expect_success(expect_s7_class(Foo(), class = Foo)) + expect_snapshot_failure(expect_s7_class(Foo(), class = Bar)) + + Baz <- S7::new_class("Baz", parent = Foo) + expect_snapshot_failure(expect_s7_class(Baz(), class = Bar)) +})