R tryCatch with testthat expectation
Asked Answered
L

1

5

I have the following function:

fun = function(expr) {
  mc = match.call()

  env = as.environment(within(
    list(),
    expr = eval(mc$expr)
  ))

  return(env)
}

that gets called within a tryCatch() so that any error conditions in expr are handled gracefully.

It works fine with a standard error condition:

tryCatch({
  fun({
    stop('error')
  })
}, error = function(e) {
  message('error happened')
})

# error happened

However, it does not capture testthat expectation errors (which is preferred for my specific use case):

library(testthat)

tryCatch({
  fun({
    expect_true(FALSE)
  })
}, error = function(e) {
  message('expectation not met')
})

# Error: FALSE isn't true.

or more simply:

library(testthat)

tryCatch({
  expect_true(FALSE)
}, error = function(e) {
  message('expectation not met')
})

# Error: FALSE isn't true.

The expectation error is not caught.

This issue appeared after upgrading from R 3.2.2 to R 3.3.0 - i.e. expectation errors were caught just fin in R 3.2.2.

Is there a way to make testthat expectations caught by tryCatch() in R 3.3.0?

Loup answered 16/6, 2016 at 23:24 Comment(2)
The expect_xxx functions call expect which in turn calls withRestarts. I don't know exactly what that does but it seems to be the root of the issue you're having.Giacopo
@Giacopo Yes, I did notice that. I'm not sure if that was always the case (e.g. back when first developed my code with R 3.2.2). Ideally, I'd prefer to circumvent its current behavior without monkey patching.Loup
P
10

I set the debugger on expect() then stepped through a couple of lines of code (output edited for brevity), and looked at the class of the condition being signaled

> debug(expect)
> xx = expect_true(FALSE)
...
Browse[2]> n
debug: exp <- as.expectation(exp, ..., srcref = srcref)
Browse[2]>
...
Browse[2]> class(exp)
[1] "expectation_failure" "expectation"         "condition"   
Browse[2]> Q
> undebug(expect)       

so it is not a condition of class 'error', and can be caught explicitly

> tryCatch(expect_true(FALSE), expectation_failure=conditionMessage)
[1] "FALSE isn't true.\n"

You could also catch the expectation class.

The restart allows you to continue, if that were somehow important.

result <- withCallingHandlers({
    expect_true(FALSE)
    expect_true(!TRUE)
    expect_true("this sentence")
}, expectation_failure=function(e) {
    cat(conditionMessage(e))
    invokeRestart("continue_test")
})

with output

FALSE isn't true.
!TRUE isn't true.
"this sentence" isn't true.
> result
[1] FALSE

One could also catch or handle successes

> tryCatch(expect_true(TRUE), expectation_success=class)
[1] "expectation_success" "expectation"         "condition"  
Parotitis answered 17/6, 2016 at 1:56 Comment(1)
Thanks! It certainly wasn't clear to me from the tryCatch() documentation that you could specify handlers for specific classes. Another one to add to my list of R tricks.Loup

© 2022 - 2024 — McMap. All rights reserved.