Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Assert.AreEqual(x, y) fail, but Assert.AreEqual(y, x) doesn't?

Consider this struct:

public struct MyNumber
{
    private readonly int _value;

    public MyNumber(int myNumber)
    {
        _value = myNumber;
    }

    public int Value
    {
        get { return _value; }
    }

    public override bool Equals(object obj)
    {
        if (obj is MyNumber)
            return this == (MyNumber) obj;

        if (obj is int)
            return Value == (int)obj;

        return false;
    }

    public override string ToString()
    {
        return Value.ToString();
    }

    public static implicit operator int(MyNumber myNumber)
    {
        return myNumber.Value;
    }

    public static implicit operator MyNumber(int myNumber)
    {
        return new MyNumber(myNumber);
    }
}

When I do this in a unit test:

Assert.AreEqual(new MyNumber(123), 123);

It's green.

But this test fails:

Assert.AreEqual(123, new MyNumber(123));

Why is this so? I'm guess it's because the int class determines the equality, whereas in the first case, my class determines it. But my class is implicitly convertible to int, so shouldn't that help?

How can I make the Assert.AreEqual work in both ways? I'm using MSTest by the way.

Update

Implementing IEquatable<int> or IComparable<int> doesn't help.

like image 585
Peter Avatar asked Feb 12 '23 03:02

Peter


1 Answers

The first assertion will invoke MyNumber.Equals and you have implemented in a way where it will succeed if the argument to compare to is an Int32 and it is equal to value of MyNumber.

The second assertion will invoke Int32.Equals and it will fail because the argument to compare to is an MyNumber which Int32 does not know about or understand.

You cannot make your second unit test succeed because you assert that an Int32 should be equal to your value. It cannot be because it is different. It is the code in Int32.Equals that decides if the second value is equal. You have implemented some casting operators that you can unit test so these assertions should work:

Assert.AreEqual(123, (int) new MyNumber(123));
Assert.AreEqual((MyNumber) 123, new MyNumber(123));

Even though the casts are implemented implicit they will not automatically be invoked by Assert.AreEquals because this method expect two parameters of type Object and you will have to invoke them explicitly as I did above.

Because of the special handling in your MyNumber.Equals you now have a type that is not commutative with regards to equals, e.g. MyNumber(123) equals Int32(123) is true but Int32(123) equals MyNumber(123) is false. You should avoid that so I recommend that you remove the special case handling of ints in MyNumber.Equals and instead rely on the implicit casts which will work for you most of the time. And when they do not you will have to make an explicit cast.

like image 168
Martin Liversage Avatar answered Feb 15 '23 10:02

Martin Liversage