Peter
Peter

Reputation: 14118

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.

Upvotes: 4

Views: 1360

Answers (3)

Martin Liversage
Martin Liversage

Reputation: 106916

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.

Upvotes: 6

Rob Epstein
Rob Epstein

Reputation: 1500

Implement IComparable<int> in your MyNumber struct as follows:

    public int CompareTo(int other)
    {
        return other.CompareTo(Value);
    }

This will ensure all Asserts work as expected such as:

    Assert.AreEqual(new MyNumber(123), 123);
    Assert.AreEqual(123, new MyNumber(123));
    Assert.Greater(124, new MyNumber(123));
    Assert.Less(124, new MyNumber(125));

Upvotes: 0

user743382
user743382

Reputation:

To quote from Object.Equals:

The following statements must be true for all implementations of the Equals(Object) method. In the list, x, y, and z represent object references that are not null.

  • ...

  • x.Equals(y) returns the same value as y.Equals(x).

Your Equals implementation breaks this hard requirement. ((object)x).Equals(123) must return the same value as ((object)123).Equals(x), if x is not null, regardless of what type it has.

There's a whole lot of code out there that, correctly, assumes that it doesn't matter which of the two objects is asked to perform the comparison. Design your code so that that assumption is not invalidated.

Effectively, this means that you must design your class in such a way that it doesn't compare equal to any integer type, no matter how much you might prefer otherwise.

Upvotes: 2

Related Questions