如何设置调用具有特定类型的等于覆盖MoQ中的等于?

时间:2009-09-25 09:02:09

标签: unit-testing mocking moq equals

使用精美的模拟框架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 }})。因此测试失败了。

到目前为止,我没有办法解决这个相当常见的模式,除了重写类不使用重写等号。

我不喜欢那样。

有人有什么想法吗?

4 个答案:

答案 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并改为使用容器。这样你就可以实现两件事:

  1. 您可以通过Mock<ISomeClass>轻松模拟它,实际上只是对容器的功能进行单元测试
  2. 通过实际单元测试容器与测试SomeClass类功能的实际实现分开来更好地进行联合测试。