Thorkil Værge
Thorkil Værge

Reputation: 3787

How to test the type of an error in Box<dyn Error>?

I have a function that returns Result<(), Box<dyn Error>>. I am writing a test case for this function where the function should return an error of variant VerifyError::LinearCombinationError (playground):

use std::error::Error;
use std::fmt::{Debug, Display, Formatter};

#[derive(Debug, PartialEq, Eq)]
enum VerifyError {
    LinearCombination,
}

impl Error for VerifyError {}

impl Display for VerifyError {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

fn return_result() -> Result<(), Box<dyn Error>> {
    Err(Box::new(VerifyError::LinearCombination))
}

pub fn main() {
    let res = return_result();
    println!("debug print is: {:?}", res);

    // Program does not compile when below line is not commented out
    // assert_eq!(Some(VerifyError::LinearCombination), res.err());
}

Uncommenting the line gives the error message:

error[E0308]: mismatched types
  --> src/main.rs:26:5
   |
26 |     assert_eq!(Some(VerifyError::LinearCombination), res.err());
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `VerifyError`, found struct `Box`
   |
   = note: expected enum `Option<VerifyError>`
              found enum `Option<Box<dyn std::error::Error>>`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

I am using Rust 1.53.0.

Upvotes: 7

Views: 3868

Answers (2)

andersonjwan
andersonjwan

Reputation: 116

You can also use the is_ok and is_err functions of the Result type to test the type of variant if you don't care about the specific error itself.

An example would be:

#[cfg(test)]
mod tests {
    #[test]
    fn should_return_error() {
        assert!(super::return_result().is_err());
    }
}

Note: It's assumed the return_result function is defined in the parent module. Therefore, it can be accessed with super.

Upvotes: 0

Shepmaster
Shepmaster

Reputation: 430941

Use Error::is to test if the error trait object has a specific concrete type or Error::downcast to convert it to that concrete type:

use std::error::Error;

fn example() -> Result<(), Box<dyn Error>> {
    Err(std::io::Error::last_os_error().into())
}

#[test]
fn is_from_io_error() {
    let e = example().err().unwrap();
    assert!(e.is::<std::io::Error>());

    let _e = e.downcast::<std::io::Error>().unwrap();
}

If you care about the specific error, I'd encourage you to avoid using trait objects. The point of trait objects is that you don't need to know what the concrete type is.

Instead, you could use a library that helps you construct error types, such as my SNAFU. This usage shows wrapping errors in an enum that can be pattern matched to test the specific type:

use snafu::{Snafu, ResultExt}; // snafu = "0.7.0-beta.0"

#[derive(Debug, Snafu)]
enum Error {
    Example { source: std::io::Error }
}

fn example() -> Result<(), Error> {
    Err(std::io::Error::last_os_error()).context(ExampleSnafu)
}

#[test]
fn is_from_io_error() {
    let e = example();
    assert!(matches!(e, Err(Error::Example { .. })))
}

Upvotes: 12

Related Questions