Is there a testthat expect_* function for comparing two objects behavior?

Viewed 129

I am writing some unit tests for an R package using testthat. I would like to compare two objects where not all the details need to match, but they must maintain equivalence with respect to a set of functions of interest.

For a simple example, I want to use something like

library(testthat)

x <- 1:4
y <- matrix(4:1, nrow=2)

test_that("objects behave similarly", {
  expect_equal_applied(x, y, .fn=list(sum, prod))

  ## which would be shorthand for:
  ## expect_equal(sum(x), sum(y))
  ## expect_equal(prod(x), prod(y))
})

In practice, x and y might be S3 objects, not simply base data structures.

Obviously, this is simple to implement, but I'd prefer something idiomatic if already existing. So, the question is, does testthat implement an expect function like this?

Searching through the API, nothing struck me as fitting this description, but it seems like a natural pattern. Or maybe there is a reason why such a pattern is objectionable that I'm overlooking.

1 Answers

Looking at the documentation {testthat} has currently (third edition) no function like expect_equal_applied. But, as you mention already, we can construct such a function easily:

library(testthat)

x <- 1:4
y <- matrix(4:1, nrow=2)


expect_equal_applied <- function(object, expected, fns) {
  fns <- purrr::map(fns, rlang::as_function)
  purrr::map(fns, ~ expect_equal(.x(object), .x(expected)))
}

test_that("objects behave similarly", {
  expect_equal_applied(x, y, fns = list(sum, prod))
})
#> Test passed

x <- 1:3

test_that("objects behave similarly", {
  expect_equal_applied(x, y, fns = list(sum, prod))
})

#> -- Failure (<text>:19:3): objects behave similarly -----------------------------
#> .x(object) not equal to .x(expected).
#> 1/1 mismatches
#> [1] 6 - 10 == -4
#> Backtrace:
#>  1. global::expect_equal_applied(x, y, fns = list(sum, prod))
#>  2. purrr::map(fns, ~expect_equal(.x(object), .x(expected)))
#>  3. .f(.x[[i]], ...)
#>  4. testthat::expect_equal(.x(object), .x(expected))
#> 
#> -- Failure (<text>:19:3): objects behave similarly -----------------------------
#> .x(object) not equal to .x(expected).
#> 1/1 mismatches
#> [1] 6 - 24 == -18
#> Backtrace:
#>  1. global::expect_equal_applied(x, y, fns = list(sum, prod))
#>  2. purrr::map(fns, ~expect_equal(.x(object), .x(expected)))
#>  3. .f(.x[[i]], ...)
#>  4. testthat::expect_equal(.x(object), .x(expected))

Created on 2021-09-17 by the reprex package (v2.0.1)

Regarding why such a function seems to be missing in {testthat}, I think that it isn't really necessary given, that we can construct it with lapply or map.

Related