How do you assert enums?

I have an enum for Opcodes, like so:

enum Opcode {
  Add,
  Sub,
  Mul,
  Div,
  etc...
};

And I’m trying to test that a struct containing the opcode as a field has the correct enum. How do I assert it in a test? The following doesn’t work.

fn x_n_test() {
    let node = x_n();
    assert_eq!(node.opcode, Opcode::X);
}
2 Likes

assert_eq! needs (naturally) an == operator, thus also an PartialEq implementation for operands. For enums, prepending #[derive(PartialEq)] is the easiest way to get it.

5 Likes

I’m pretty new to Rust. While it makes sense I need to implement a == operator, I’m not sure what you mean by PartialEq implementation. Do you have a short example?

1 Like

Ah, probably that needs a knowledge about traits. :slight_smile: If you don’t know about traits, for now, you can just assume that == needs the following:

#[derive(PartialEq)] // <- this line has been added
enum Opcode {
    ...
}

(You can also get > or < with #[derive(PartialOrd)]. There are tons of other things like this available with #[derive]. Not every enum can be empowered with this way though, and that’s when you need to understand traits…)

1 Like

Ah, it’s like a preprocessor that adds methods. I’ll look into derives. Thanks for the lead!

1 Like

There’s also a matches! macro in the matches crate:

assert!(matches!(node.opcode, Opcode::X));
1 Like

Nice! But with it, I get an error.

src/libfab/tree/node.rs:603:51: 603:52 error: unexpected end of macro invocation
src/libfab/tree/node.rs:603         assert_eq!(matches!(node.opcode, Opcode::X));
                                                                              ^

Seems to be pointing to the second to last parens.

Sorry, I meant assert!. The unfortunate problem with macros is that the error messages are terrible. Also, it appears you need to import the _tt_as_expr_hack macro as well…

#[macro_use(matches, _tt_as_expr_hack)]
extern crate matches;

enum Opcode {
    X,
    Y,
}

#[test]
fn it_works() {
    assert!(matches!(Opcode::X, Opcode::X));
}
2 Likes

Newbie here, I just start to learn how to create test and it worked after add #[derive(PartialEq, Debug)], here is my complete example.

#[derive(PartialEq, Debug)]
enum Language {
    ENGLISH,
    PORTUGUESE,
}

fn select_language(language: Language) -> Language {
    match language {
        Language::ENGLISH => println!("English selected"),
        Language::PORTUGUESE => println!("Portuguese selectionado"),
    }

    language
}

fn main() {
    select_language(Language::ENGLISH);
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn internal() {
        assert_eq!(Language::ENGLISH, select_language(Language::ENGLISH));
        assert_eq!(Language::PORTUGUESE, select_language(Language::PORTUGUESE));
    }
}

deriving PartialEq is the best solution in this specific case, though another hack that doesn’t require it (nor any other macro) would be

enum Opcode {
    Add,
    Mul,
    HaltAndCatchFire,
}
fn main() {
    let opcode = Opcode::Mul;
    assert!(if let Opcode::Add = opcode { true } else { false })
}

While being ofc correct, I think those hacks are misleading. The best way to solve the problem imo is deriving PartialEq.
This allows you to use == with your enum, which might be useful apart from this usecase as well.

a == b in rust is just syntactic sugar for std::cmp::PartialEq::eq(a, b)
So in order to be able to use == or assert_eq!, you have to tell Rust that you implement this PartialEq trait that provides this method. You could either do this manually, or tell Rust to just do it the “default” way with this #[derive(PartialEq)] line before your enum declaration

1 Like

Another alternative is the assert_matches crate. This provides a better error message via Debug if the match fails, which is very useful when testing enums that contain data:

#[cfg(test)]
#[macro_use]
extern crate assert_matches;

#[derive(Debug)]
pub enum Opcode {
    X { i: i32 },
    Y,
}

#[test]
fn it_works() {
    let a = Opcode::X { i: 5 };
    assert_matches!(a, Opcode::X { i: 1..=4 });
}

thread 'it_works' panicked at 'assertion failed: `X { i: 5 }` does not match `Opcode::X { i: 1 ..=4 }`', src/lib.rs:14:5
6 Likes

This thread is still the first result on Google for "rust assert enum" without quotes so I feel right reviving it.

I've found that using type_id() seems to work, but is this safe to do? Example:

use std::any::Any;

enum Opcode {
  Add,
}

struct Node {
    opcode: Opcode
}

fn x_n() -> Node {
    Node { opcode: Opcode::Add }
}

fn main() {
    let node = x_n();
    assert_eq!(node.opcode.type_id(), Opcode::Add.type_id());
}

I specifically needed this because my enum wraps an io::Error and so I can't simply derive PartialEq on it.

Enum variants are not distinct types, what you're comparing is the type_id of Opcode to the type_id of Opcode, meaning the assert never fails. You can try adding a new Opcode and changing the comparison to something that should fail to see that it doesn't work. What you want is matches!:

assert!(matches!(node.opcode, Opcode::Sub));
2 Likes

You're right here. I wasn't testing well enough. Thanks!

Note that the assert_matches crate Nemo157 linked above is such a popular choice that the standard library is currently in the process of stabilizing their own version of this macro.

For progress on that, see the tracking issue #82775.

For people that land in this thread via Google in the future, it might be stable by the time you read this.

4 Likes

This topic was automatically closed 7 days after the last reply. We invite you to open a new topic if you have further questions or comments.