How to assert io errors in Rust?
Solution 1
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):
- snafu (my favorite)
- thiserror
- anyhow
- quick-error
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);
}
Solution 2
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(_))));
Solution 3
If you use the type Result
,
there is a built in method is_err()
.
This means you can test with assert!(my_result.is_err());
.
Solution 4
In my case I had several structs with differents fields, they all implement the std::error::Error
trait. This worked for me in a cleaner way than making an instance of any struct I needed:
let result = parse_data(1);
let your_error = result.unwrap_err().downcast_ref::<MyCustomError>();
assert!(your_error.is_some());
That downcasts the error to a specific type, in case it's the correct type it's a Some(_)
value, otherwise, it's None
. However, I'm a newbie in Rust, it's probably that this is not the best way to solve your problem.
Solution 5
A macro using Genaritos answer.
macro_rules! is_error_type {
($result:ident,$err_type:ident) => {
$result.unwrap_err().downcast_ref::<$err_type>().is_some()
}
}
used like this:
let result = parse_data(1);
assert!(is_error_type(result, MyCustomError));
Related videos on Youtube
wst
Kotlin/Java/Go - Software engineering, architecture, design, implementation, testing. Brief technology list: Go, Elasticsearch, NSQ, JavaSE/EE, Kotlin, Spring Boot, Spring Cloud, microservices, distributed systems architecture, devops, docker, container orchestration, Kubernetes, Azure, GoogleCloud, CosmosDB, Cloud Spanner, MySQL, MongoDB, Couchbase, RabbitMq, event sourcing and CQRS - ready to learn more. Mac, Windows and Linux user. Also Ruby/Rails, JSF, Primefaces, HTML, PHP, Javascript, basics of web development. Previous position: Monitoring and first stage diagnosis of major outages and international services and backbone network for GTS CE with wide knowledge of DWDM, SDH, microwave and IP systems, Ethernet L2. Cooperation with international team, close cooperation with colleagues in regards to organize team work, procedures and everyday work. Graphics edition (with a high sense of aesthetic).
Updated on July 08, 2022Comments
-
wst almost 2 years
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 implementPartialEq
trait by default so you can't useassert_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?
-
grooveplex over 4 yearsI have a function returning
Result<String, ParseError>
, I added#[derive(PartialEq)]
toParseError
and it worked! -
BenSmith over 4 yearsI found this post very helpful, although I have just a small nit that the expected and result should be swapped in the assertion [i.e. assert_eq!(result, expected);]. I believe this varies between different programming languages!
-
Shepmaster over 4 years@BenSmith Rust specifically does not ascribe meaning to the first or second argument of
assert_eq!
. That's why the failure text refers to them asleft
andright
. -
BenSmith over 4 yearsTouché! After double checking, it was the IntelliJ IDE returning Expected and Actual in failing tests when run via the “play button”. Indeed one would see left and right when executing the standard (intended) cargo run.
-
Stargateur over 2 yearsthe question already contains this advice
assert!(result.is_err());
the question is about how to check the expected error is returned -
hoijui over 2 yearsThis is more powerful then the answers that just check the type, as it also contains the content. Doing that is considered to be an anti-pattern in rust, though, as content (like error strings) can change, and do not allow for compile time verification.
-
scoopr over 2 yearsWell in my case, I actually wanted to test for the content (that the error was from the identifier I expected), which I don't see as anti-pattern at all, I'm not comparing against the
Display
string. But the pattern match can of course just omit the comparison, if you don't care about the value. I've amended the answer with an example of that. -
Sam Myers over 2 yearsIf your error type doesn't have a
.kind()
, ie. if usingthiserror
crate, use @scoopr's answer. -
Stargateur over 2 years@SamMyers not really, the question is about how to assert error in Rust, the general answer is error should implement PartialEq, the
.kind()
part of my answer is just specific to the OP, sccopr answer is unclear for me. -
Stargateur over 2 yearsany reason to not write
assert!(result, Err(crate::Error::InvalidType("foobar")));
? I don't understand your answer please provide a minimal reproducible example -
Stargateur over 2 yearsusing io::Error, to make its own error is probably a bad idea, look for something like snafu crate
-
Genarito over 2 yearsHi! Why It would be a bad idea?
-
Stargateur over 2 yearsio::Error, have never been design to be use that way, it should only be use for io error at max. also downcast on error look... bad. If the error is an opaque type, this mean the error should be used as opaque so no downcast, if your error must be usable for user, then it should not be a opaque type so not a dyn Error but an enum of error possibility. I don't fully agree with lpalmieri.com/posts/error-handling-rust but it's mostly a good guide.
-
Genarito over 2 yearsBut I use
std::error::Error
. Would it have the same problem? -
Stargateur over 2 yearswell, as I said, if you wrap all your error into opaque type
std::error::Error
you should NOT need to do what you do in your answer. Cause you choice to make error opaque. I recommend you to read the guide I link and pick either snafu or thiserror crate. You may be interested by anyhow crate if you want to keep your error opaque. -
Genarito over 2 yearsThanks for the reference! I'll check how I could improve my crates!
-
scoopr over 2 yearsIf you mean
assert_eq
, then that requiresPartialEq
Impl, which my enum didn’t trivially have, and as I pointed out, this solution doesn’t require it. Which is elegant in that it wasn’t needed outside the test anyway. -
M-Cat almost 2 yearsThanks,
matches!
is just what I needed. Not all errors (e.g.TryFromSliceError
) implementPartialEq
! I don't know why, but not much I could do about it apart from work around it. -
M-Cat almost 2 yearsAlso, looks like there is
assert_matches
in nightly. doc.rust-lang.org/std/assert_matches/macro.assert_matches.html