Is there a way to get fully informative errors using testthat with nested for-loops?

Viewed 21

I have a number of dataframes with similar structures that I need to unit test. I would like to use nested for-loops to run the tests: for each column in each data frame, perform a series of tests.

However, I'm finding that the limited quasi-label capabilities in testthat are preventing fully informative errors.

library(testthat)
example1 <- data.frame("A" = c(1, 2, 3, 4),
                  "B" = c(5, 6, 7, 8))

example2 <- data.frame("A" = c(5, 6, 7, 8),
                  "B" = c(1, 2, 3, 4))

test_that("Generic Example", {
  for(df in list(example1, example2)) {
    for(col in names(df)) {
      # Error: df[["A"]] has type 'double', not 'character'.
      expect_type(df[[!!col]], "character")
      # Error: c(1, 2, 3, 4) has type 'double', not 'character'.
      expect_type(!!df[[col]], "character")
    }
  }
})
#> ── Failure (<text>:12:7): Generic Example ──────────────────────────────────────
#> df[["A"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:14:7): Generic Example ──────────────────────────────────────
#> c(1, 2, 3, 4) has type 'double', not 'character'.
#> 
#> ── Failure (<text>:12:7): Generic Example ──────────────────────────────────────
#> df[["B"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:14:7): Generic Example ──────────────────────────────────────
#> c(5, 6, 7, 8) has type 'double', not 'character'.
#> 
#> ── Failure (<text>:12:7): Generic Example ──────────────────────────────────────
#> df[["A"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:14:7): Generic Example ──────────────────────────────────────
#> c(5, 6, 7, 8) has type 'double', not 'character'.
#> 
#> ── Failure (<text>:12:7): Generic Example ──────────────────────────────────────
#> df[["B"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:14:7): Generic Example ──────────────────────────────────────
#> c(1, 2, 3, 4) has type 'double', not 'character'.
#> Error in `reporter$stop_if_needed()`:
#> ! Test failed

An example of the failure message that I'd prefer:

example1[["A"]] has type 'double', not 'character'.

I found a partial workaround if I pass a character vector instead of a list of dataframes and nest test_that:

for(df_name in c('example1', 'example2')) {
  test_that(paste("Now testing", df_name), {
    df <- get0(df_name)
    for(col in names(df)) {
      expect_type(df[[!!col]], "character")
      # Error: c(1, 2, 3, 4) has type 'double', not 'character'.
      expect_type(!!df[[col]], "character")
    }
  })
}
#> ── Failure (<text>:23:7): Now testing example1 ─────────────────────────────────
#> df[["A"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:25:7): Now testing example1 ─────────────────────────────────
#> c(1, 2, 3, 4) has type 'double', not 'character'.
#> 
#> ── Failure (<text>:23:7): Now testing example1 ─────────────────────────────────
#> df[["B"]] has type 'double', not 'character'.
#> 
#> ── Failure (<text>:25:7): Now testing example1 ─────────────────────────────────
#> c(5, 6, 7, 8) has type 'double', not 'character'.
#> Error in `reporter$stop_if_needed()`:
#> ! Test failed

However, I'd still prefer a fully self-contained error message. Any thoughts?

Using testthat version 3.1.4, the latest available as of this post.

0 Answers
Related