Moq Equals Only Works With IEquatable

会有一股神秘感。 提交于 2019-12-13 14:54:14

问题


I'm using the Moq framework for unit tests, and I came across this interesting problem.

public interface Bar : IEquatable<Bar>
{
}

[TestClass]
public class TestClass
{
    Mock<Bar> a;
    Mock<Bar> b;

    public TestClass()
    {
        a = new Mock<Bar>();
        b = new Mock<Bar>();

        a.Setup(bar => bar.Equals(b.Object)).Returns(true);
    }

    [TestMethod]
    public void AssertEqualsTest()
    {
        Assert.AreEqual(a.Object, b.Object); //fails
    }

    [TestMethod]
    public void AssertIsTrueTest()
    {
         Assert.IsTrue(a.Object.Equals(b.Object)); //passes
    }
}

First Issue

So Assert.AreEqual just fails. I don't want to have to use the line from the second test every time I need to check equality even though most (if not all) of my classes inherit from IEquatable.

You might think it fails because Setup is only setting the IEquality.Equals() function (which Assert.AreEqual probably doesn't check), but if you add the line

a.Setup(x => x.Equals((object)b.Object)).Returns(true);

to the constructor, it still fails.

Second Issue

If you comment out the : IEquatable<Bar> from the interface declaration (so that a.Setup overwrites object.Equals), both tests fail.

My desired outcome is to be able to setup equals on a Mock object and call Assert.AreEqual.


回答1:


First issue

Checked via dotPeek. Assert.AreEqual calls the static method object.Equals to compare the instances. object.Equals uses operator == first and since the mock instance does not implement that operator, this will default to comparing references. Clearly, a and b are different instances so the comparison returns false.

Second issue

I haven't looked at the internals of Moq but I assume this happens because the interface does not declare an Equals method. Confirmed with the following (which succeeds):

public interface IBar
{
}

public class Bar : IBar
{
    public override bool Equals(object obj)
    {
        return false;
    }
}

[TestClass]
public class Class1
{
    [TestMethod]
    public void TestMoq()
    {
        var a = new Mock<Bar>();
        var b = new Mock<Bar>();

        a.Setup(bar => bar.Equals(b.Object)).Returns(true);

        Assert.IsTrue(a.Object.Equals(b.Object));
    }
}

If I remove the Bar.Equals override, the test will also fail. Just a guess but since Moq uses Castle internally, this issue can be explained by this Q&A.

Anyway, I think what you're doing now with Assert.IsTrue(a.Object.Equals(b.Object)); and IEquatable is a sufficient workaround for this.

By the way, as JaredPar asked above, why are you comparing mocks?




回答2:


I used..

mockLine2.CallBase = True

And Why?

We're testing an order service. As order has two lines, service removes any line matching some criteria. Lines are an IList, List (etc) uses .equals() to find the item you want, so to remove the line, you need an implementation of equals that passes:

Assert.IsTrue(mockLine2.Object.Equals(mockLine2.Object)

"Moq mock isn't detecting that a line equals itself, so the line collection on the order will fail to find the line.")

You could argue that as Order isn't the class under test we should mock the order and assert that order.Lines.Remove() is called with the right line, but we've found it on balance less painful to use the real domain objects (they're all fairly dumb) than mock them.



来源:https://stackoverflow.com/questions/10826199/moq-equals-only-works-with-iequatable

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!