57

I'd like to check enums with fields in tests while ignoring the actual value of the fields for now.

Consider the following example:

enum MyEnum {
    WithoutFields,
    WithFields { field: String },
}

fn return_with_fields() -> MyEnum {
    MyEnum::WithFields {
        field: "some string".into(),
    }
}

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

    #[test]
    fn example() {
        assert_eq!(return_with_fields(), MyEnum::WithFields {..});
    }
}

playground

I'd like to use assert_eq! here, but the compiler tells me:

error: expected expression, found `}`
  --> src/lib.rs:18:64
   |
18 |         assert_eq!(return_with_fields(), MyEnum::WithFields {..});
   |                                                                ^ expected expression

This is similar to Why do I get an error when pattern matching a struct-like enum variant with fields?, but the solution does not apply in my case.

Of course, I can use match and do it myself, but being able to use assert_eq! would be less work.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Philipp Ludwig
  • 3,758
  • 3
  • 30
  • 48
  • What `MyEnum::WithFields {..}` is suppose to do ? https://play.rust-lang.org/?gist=dfa1b92d4a88f83273cb0bb95f226612&version=stable&mode=debug&edition=2015 – Stargateur Jul 01 '18 at 08:08
  • 4
    Hi :) I think your question is answered by ["Compare enums only by variant, not value"](https://stackoverflow.com/questions/32554285/compare-enums-only-by-variant-not-value). In short: use [`mem::discriminant`](https://doc.rust-lang.org/std/mem/fn.discriminant.html). If you don't think this link answers your question, please explain why your question is different. – Lukas Kalbertodt Jul 01 '18 at 08:15
  • @Stargateur it is supposed to ignore the fields value and just match on ``WithFields``. – Philipp Ludwig Jul 01 '18 at 08:29
  • @LukasKalbertodt Sadly that does not work, the rust compiler still wants me to specify the field somehow. – Philipp Ludwig Jul 01 '18 at 08:29
  • @PhilippLudwig You can't compare a variant with something that doesn't exist, do you want something like that ? https://play.rust-lang.org/?gist=e7df26067b37cbeea7624a181645dd3a&version=stable&mode=debug&edition=2015 – Stargateur Jul 01 '18 at 08:35
  • @Stargateur yeah, I'd like something like that. Seems a bit much though. – Philipp Ludwig Jul 01 '18 at 10:11

3 Answers3

67

Rust 1.42

You can use std::matches:

assert!(matches!(return_with_fields(), MyEnum::WithFields { .. }));

Previous versions

Your original code can be made to work with a new macro:

macro_rules! is_enum_variant {
    ($v:expr, $p:pat) => (
        if let $p = $v { true } else { false }
    );
}

#[test]
fn example() {
    assert!(is_enum_variant!(return_with_fields(), MyEnum::WithoutFields {..}));
}

Personally, I tend to add methods to my enums:

fn is_with_fields(&self) -> bool {
    match self {
        MyEnum::WithFields { .. } => true,
        _ => false,
    }
}

I also tend to avoid struct-like enums and instead put in extra work:

enum MyEnum {
    WithoutFields,
    WithFields(WithFields),
}

struct WithFields { field: String }

impl MyEnum {
    fn is_with_fields(&self) -> bool {
        match self {
            MyEnum::WithFields(_) => true,
            _ => false,
        }
    }

    fn as_with_fields(&self) -> Option<&WithFields> {
        match self {
            MyEnum::WithFields(x) => Some(x),
            _ => None,
        }
    }
    
    fn into_with_fields(self) -> Option<WithFields> {
        match self {
            MyEnum::WithFields(x) => Some(x),
            _ => None,
        }
    }
}

I hope that some day, enum variants can be made into their own type to avoid this extra struct.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    A gotcha I ran into with `assert!(matches!(...))` is that, unlike `assert_eq!`, the argument order matters, and you must use a literal for the second argument. `assert!(matches!(exp_result, act_result))` will never panic, because `exp_result` can always be matched with the "name" `act_result`. You need to destructure at least one level in order to trigger an actual match, like the presented `assert!(matches!(exp_result, MyEnum::WithFields { .. }))`. Somewhat obvious in hindsight, but maybe this comment will help someone save a few minutes of debugging. – Sean Kelleher May 22 '22 at 14:21
  • @SeanKelleher You'd also get a warning from the compiler: `warning: unused variable: \`act_result\`` – Shepmaster Jun 21 '22 at 16:57
8

If you are using Rust 1.42 and later, see Shepmaster's answer below.

A simple solution here would be to do the opposite assertion:

assert!(return_with_fields() != MyEnum::WithoutFields);

or even more simply:

assert_ne!(return_with_fields(), MyEnum::WithoutFields);

Of course if you have more members in your enum, you'll have to add more asserts to cover all possible cases.

Alternatively, and this what OP probably had in mind, since assert! just panics in case of failure, the test can use pattern matching and call panic! directly in case something is wrong:

match return_with_fields() {
    MyEnum::WithFields {..} => {},
    MyEnum::WithoutFields => panic!("expected WithFields, got WithoutFields"),
}
SirDarius
  • 41,440
  • 8
  • 86
  • 100
  • Thanks, that is a good idea. I'm still wondering though if there is a more straight-forward solution. Or maybe I will just have to create my own macro. – Philipp Ludwig Jul 01 '18 at 10:12
  • @SirDarius Thanks for providing the source, this is interesting. I will accept your answer, maybe you want to include these additional information there. – Philipp Ludwig Jul 01 '18 at 12:08
2

I'd use a macro like @Shepmaster proposed, but with more error reporting (like the existing assert! and assert_eq! macros:

macro_rules! assert_variant {
    ($value:expr, $pattern:pat) => ({
        let value = &$value;

        if let $pattern = value {} else {
            panic!(r#"assertion failed (value doesn't match pattern):
   value: `{:?}`,
 pattern: `{}`"#, value, stringify!($pattern))
        }
    })

    // TODO: Additional patterns for trailing args, like assert and assert_eq
}

Rust playground demonstrating this example

Lucretiel
  • 3,145
  • 1
  • 24
  • 52