问题
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