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.