There are plenty of tutorials showing how to write unit tests in Rust. I've read dozen and all of them focuses on asserting values in case of success. Situation doesn't seem so straight forward in case of an error
. Errors do not implement PartialEq
trait by default so you can't use assert_eq!
macro. Also, some functions may return multiple variants of error depending on what kind of issue occurred (ex. io::Error
which may be of different kind.) I could just check if error occurred or not but it doesn't seem enough.
Example below.
fn parse_data(input: i32) -> Result<i32, io::Error> { match input { 0 => Ok(0), _ => Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected number")) } } #[test] fn test_parsing_wrong_data() { let result = parse_data(1); assert!(result.is_err()); let got = result.unwrap_err(); let want = io::Error::new(io::ErrorKind::InvalidData, "unexpected number"); // compilation error here: binary operation `==` cannot be applied to type `std::io::Error` assert_eq!(want, got); }
I assume this is not idiomatic approach, since it's not compiling. Hence the question - what is a proper and idiomatic approach in similar situation?
If you want the test to fail, just put #[should_fail] under #[test] . To run unit tests, add either the --test or --cfg test flag to the command.
At its simplest, a test in Rust is a function that's annotated with the test attribute. Attributes are metadata about pieces of Rust code; one example is the derive attribute we used with structs in Chapter 5. To change a function into a test function, add #[test] on the line before fn .
Macro std::assertAsserts that a boolean expression is true at runtime. This will invoke the panic! macro if the provided expression cannot be evaluated to true at runtime.
You'll put unit tests in the src directory in each file with the code that they're testing. The convention is to create a module named tests in each file to contain the test functions and to annotate the module with cfg(test) .
TL;DR: Error
should implement PartialEq
Result<T, E>
only implements PartialEq
when T
and E
also implement PartialEq
, but io::Error
doesn't. alex confirm that cause io::Error
takes an extra error that implements dyn Error
, allowing the user to add extra information lead std to not implement PartialEq
.
I see a lot of answer and comment that seem a lot of peoples use io::Error
to create their own error. This is NOT a good practice, io::Error
should be used only if you deal yourself with io
. There is the Error Handling Project Group if you want to learn and share your view about error in Rust.
For now there is some common crate in Rust to make your own error (feel free to add crate):
I don't totally agree but here a good guide about Error in Rust.
Anyway, the solution that you probably want in your case is just to compare the ErrorKind
value. As ErrorKind
implements PartialEq
this will compile with assert_eq()
use std::io; fn parse_data(input: i32) -> Result<i32, io::Error> { match input { 0 => Ok(0), x => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected number {}", x), )), } } #[test] fn test_parsing_wrong_data() { let result = parse_data(1).map_err(|e| e.kind()); let expected = Err(io::ErrorKind::InvalidData); assert_eq!(expected, result); }
I solved it with this
assert!(matches!(result, Err(crate::Error::InvalidType(t)) if t == "foobar"));
This solution doesn't require PartialEq
for the Error
, but still allows me to compare with the variant contents.
If you don't care about the contents of the variant, then its just
assert!(matches!(result, Err(crate::Error::InvalidType(_))));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With