Since capture_messages() was deprecated, the only way to match against multiple messages is either to use expect_snapshot() or to call the function twice, e.g. expect_message(f(), "message1") and expect_message(f(), "message2").
When running lots of tests that cover 1-2 messages, expect_snapshot seems to be a lot of overhead to add for all those different calls.
One option: expect_messages() with nomatch()
Claude has helped me build an expect_messages() function with a nomatch() helper, but it would be much better if it were built more deeply into testthat (see below).
e.g.
expect_message(my_function(), "message1", "message2", nomatch("should not match"))
Claude's expect_messages() and nomatch helpers
# Helpers to test multiple messages in sequence.
# Usage:
# expect_messages(out <- f(), "message1", "message2", nomatch("message3"))
# Note:
# Single-message tests should still use testthat::expect_message.
nomatch <- function(pattern) structure(pattern, excluded = TRUE)
expect_messages <- function(expr, ...) {
actual_messages <- character()
result <- withCallingHandlers(
expr,
message = function(m) {
actual_messages <<- c(actual_messages, conditionMessage(m))
invokeRestart("muffleMessage")
}
)
for (pattern in list(...)) {
is_excluded <- isTRUE(attr(pattern, "excluded"))
matched <- any(grepl(pattern, actual_messages))
if (is_excluded && matched) {
fail(sprintf(
"Message matched excluded pattern '%s'.\nActual messages:\n%s",
pattern,
paste("-", encodeString(actual_messages), collapse = "\n")
))
} else if (!is_excluded && !matched) {
fail(sprintf(
"No message matched pattern '%s'.\nActual messages:\n%s",
pattern,
paste("-", encodeString(actual_messages), collapse = "\n")
))
}
}
invisible(result)
}
Another option: piped/nested calls
Another alternative would be piped/nested expect_message() calls, which currently don't seem to work. Potentially because when the first one succeeds it doesn't bubble up the other messages?
e.g.
expect_message(my_function(), "some_message") |>
expect_message("some_other_message") |>
expect_no_message("this message should not exist") # Note that this can currently be run but will always succeed because expect_message swallows
Since
capture_messages()was deprecated, the only way to match against multiple messages is either to useexpect_snapshot()or to call the function twice, e.g.expect_message(f(), "message1")andexpect_message(f(), "message2").When running lots of tests that cover 1-2 messages,
expect_snapshotseems to be a lot of overhead to add for all those different calls.One option:
expect_messages()withnomatch()Claude has helped me build an
expect_messages()function with anomatch()helper, but it would be much better if it were built more deeply into testthat (see below).e.g.
Claude's expect_messages() and nomatch helpers
Another option: piped/nested calls
Another alternative would be piped/nested
expect_message()calls, which currently don't seem to work. Potentially because when the first one succeeds it doesn't bubble up the other messages?e.g.