使用精美的模拟框架MoQ,我遇到了一个有点令人惊讶的方面(我不喜欢惊喜)。 我正在嘲笑一个应该在方法调用之后添加到集合中的类,如下所示:
public class SomeClass{
}
public class Container {
private List<SomeClass> classes = new List<SomeClass>();
public IEnumerable<SomeClass> Classes {
get {
return classes;
}
}
public void addSomeClass(SomeClass instance) {
classes.Add(instance);
}
}
[Test]
public void ContainerContainsAddedClassAfterAdd() {
var mockSomeClass = new Mock<SomeClass>();
mockSomeClass.Setup(c => c.Equals(mockSomeClass.Object)).Return(true);
var Container = new Container();
Container.addSomeClass(mockSomeClass.Object);
Assert(Container.Classes.Contains(mockSomeClass.Object));
}
这很好用,模拟被添加到Container
的集合中,并且模拟上Equals
方法的设置确保IEnumerable.Contains()
返回true。
然而,总有一些复杂因素。我真正嘲笑的课程并不像我们的SomeClass
那么简单。它是这样的:
public class SomeClassOverridingEquals{
public virtual Equals(SomeClassOverridingEquals other) {
return false;
}
public override Equals(object obj) {
var other = obj as SomeClassOverridingEquals;
if (other != null) return Equals(other); // calls the override
return false;
}
}
[Test]
public void ContainerContainsAddedClassOverridingEqualsAfterAdd() {
var mockSomeClass = new Mock<SomeClassOverridingEquals>();
mockSomeClass.Setup(c => c.Equals(mockSomeClass.Object)).Return(true);
var Container = new Container();
Container.addSomeClass(mockSomeClass.Object);
Assert(Container.Classes.Contains(mockSomeClass.Object)); // fails
}
该类包含对其自身特定类型的Equals方法的覆盖,并且模拟的Setup
方法似乎无法模拟该特定方法(仅覆盖更一般的{{1 }})。因此测试失败了。
到目前为止,我没有办法解决这个相当常见的模式,除了重写类不使用重写等号。
我不喜欢那样。
有人有什么想法吗?
答案 0 :(得分:6)
我认为问题不在于Moq,而在于使用Contains扩展方法。即使您使用更具体的重载重载了Equals,Enumerable.Contains最终也会调用List<T>.Contains
,因为Classes属性确实由List<T>
支持。
List<T>.Contains
是通过调用EqualityComparer<T>.Default.Equals
来实现的,我认为这默认调用从System.Object继承的Equals方法 - 即:不是你的模拟覆盖的那个,而是一个一个System.Object作为输入。
在Reflector中仔细阅读EqualityComparer<T>.Default
的实现,似乎它对于实现IEquatable<T>
的类型有一个特殊情况,所以如果你将这个接口添加到你的类(它已经有了适当的方法)它可能表现不同。
答案 1 :(得分:1)
Mark Seemann是对的。您需要确保在正确的超载情况下暗示Moq:
mockSomeClass.Setup(c => c.Equals((object)mockSomeClass.Object)).Return(true);
答案 2 :(得分:1)
创建SomeClass
的实例有多难?使用真实物体会更有意义吗?如果可能的话,那会更好,因为它不会改变容器和类之间关系的特定行为。
或者,您可以遍历返回的集合,只查找与mock实例相同的对象。这样可以避免解决任何特殊行为。
答案 3 :(得分:0)
创建一个接口ISomeClass
并改为使用容器。这样你就可以实现两件事:
Mock<ISomeClass>
轻松模拟它,实际上只是对容器的功能进行单元测试