user7613376
user7613376

Reputation:

Making informative `stopifnot()` errors using NSE in R

I want to make informative stopifnot() errors.

I've read: http://r-pkgs.had.co.nz/tests.html (the section at the end on using NSE to make informative test error print out for the example seems relevant) and http://adv-r.had.co.nz/Computing-on-the-language.html but I cant get this to print an informative error in concise code:

e <- new.env()
e$label <- c(1,2,3)
check_in_y <- function(x, z, e) {
  stopifnot(eval(bquote(.(x) %in% e[[.(z)]])))
}

check_in_y(5,"label", e)

The output gives this (not so informative)

Error: eval(bquote(.(x) %in% e[[.(z)]])) is not TRUE

I want the error to be more informative, saying this:

Error: 5 %in% e[["label"]] is not TRUE

How can I get this to work? Or what's the best approach to achieve what I want

I know I could write an if condition not true then print my own error as an alternative, but the extra code is a hassle. I'd like to understand how to get NSE to get this to work.

Edit: My motivation from this approach came from reading hadley's comments (at http://r-pkgs.had.co.nz/tests.html):

However, if the expectation fails this doesn’t give very informative output:

expect_floor_equal("year", "2008-01-01 00:00:00")
## Error: floor_date(base, unit) not equal to as.POSIXct(time, tz = "UTC")
## Mean absolute difference: 31622400

Instead you can use a little non-standard evaluation to produce something more informative. The key is to use bquote() and eval(). In the bquote() call below, note the use of .(x) - the contents of () will be inserted into the call.

expect_floor_equal <- function(unit, time) {
  as_time <- function(x) as.POSIXct(x, tz = "UTC")
  eval(bquote(expect_equal(floor_date(base, .(unit)), as_time(.(time)))))
}
expect_floor_equal("year", "2008-01-01 00:00:00")
## Error: floor_date(base, "year") not equal to as_time("2008-01-01 00:00:00")

Upvotes: 3

Views: 1088

Answers (2)

Uwe
Uwe

Reputation: 42544

There are a number of packages on CRAN which address the issue of meaningful error messages. I have started with the assertthat and assertive packages but I'm now using checkmate for production code, especially for checking arguments to functions. BTW, checkmate also extends Hadley's testthat package.

With checkmate,

checkmate::assert_choice(5, e[["label"]])

as well as

checkmate::assert_choice(5, e$label)

return the error message:

Error: Assertion on '5' failed: Must be element of set {'1','2','3'}, but is '5'.

It can also be used in the function

check_in_y <- function(x, z, e) {
  checkmate::assert_choice(x, e[[z]])
}
check_in_y(5, "label", e)

which returns the error message:

Error in check_in_y(5, "label", e) :
Assertion on 'x' failed: Must be element of set {'1','2','3'}, but is '5'.

Upvotes: 4

Rich Scriven
Rich Scriven

Reputation: 99331

stopifnot is just a convenience function for

if(!all(condition)) stop(standard message)

For custom messages, just write the code. You can replace the stopifnot call with two lines:

check_in_y <- function(x, z, e) {
    b <- bquote(.(x) %in% e[[.(z)]])
    if(!eval(b)) stop(deparse(b), " is not TRUE", call. = FALSE)
}

check_in_y(5, "label", e)
# Error: 5 %in% e[["label"]] is not TRUE

Upvotes: 6

Related Questions