3

使用精细的模拟框架 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));
}

这很好用,mock 被添加到Container's 集合中,Equalsmock 上的方法设置确保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模拟的方法似乎无法模拟出该特定方法(仅覆盖更通用的Equals(object))。因此测试失败。

到目前为止,除了重写类以不使用覆盖的等号之外,我还没有找到解决这种非常常见的模式的方法。

我不喜欢那样。

有人有想法么?

4

4 回答 4

6

我认为问题不在于起订量,而在于包含扩展方法。即使您使用更具体的重载重载了 Equals,但 Enumerable.Contains 最终还是会调用List<T>.Contains,因为 Classes 属性实际上是由 a 支持的List<T>

List<T>.Contains是通过调用实现的EqualityComparer<T>.Default.Equals,我认为这默认调用从 System.Object 继承的 Equals 方法 - 即:不是你的模拟覆盖的方法,而是将 System.Object 作为输入的方法。

仔细阅读 Reflector中的实现EqualityComparer<T>.Default,似乎它对实现的类型有一个特殊情况IEquatable<T>,所以如果你将该接口添加到你的类(它已经有适当的方法),它的行为可能会有所不同。

于 2009-09-25T09:41:53.137 回答
1

马克·西曼是正确的。您需要确保在正确的超载时提示 Moq:

mockSomeClass.Setup(c => c.Equals((object)mockSomeClass.Object)).Return(true);
于 2009-09-28T12:33:26.467 回答
1

创建一个实例有多难SomeClass?仅使用真实对象会更有意义吗?如果可能的话,那会更好,因为它不会改变作为容器和类之间关系一部分的特定行为。

或者,您可以遍历返回的集合并查找与模拟实例相同的对象。这将避免必须解决任何专门的行为。

于 2009-10-04T22:44:56.443 回答
0

创建一个接口ISomeClass并让您的容器使用接口。这样,您将实现两件事:

  1. 您可以轻松地模拟它Mock<ISomeClass>,实际上只是对容器的功能进行单元测试
  2. 通过将实际单元测试容器与测试 SomeClass 类功能的实际实现分开,使单元测试更好。
于 2009-09-25T09:15:07.997 回答