代码之家  ›  专栏  ›  技术社区  ›  Tomas

如何设置具有特定类型的equals调用,覆盖moq中的equals?

  •  3
  • Tomas  · 技术社区  · 15 年前

    通过使用精细的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() 返回真。 但是总有一些复杂的情况。我真正嘲笑的班级并不像我们的班那么简单。 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) )因此测试失败。

    到目前为止,我还没有找到解决这个非常常见的模式的方法,除了重写类以不使用重写的equals。

    我不喜欢那样。

    有人有什么想法吗?

    4 回复  |  直到 12 年前
        1
  •  6
  •   Mark Seemann    15 年前

    我认为问题不在于moq,而在于contains扩展方法。即使您已使用更具体的重载重载重载了Equals,但Enumerable.Contains最终会调用 List<T>.Contains 因为Classes属性实际上由 List<T> .

    列表<t>。包含 通过调用实现 EqualityComparer<T>.Default.Equals 我认为这默认为调用从System.Object继承的equals方法——也就是说:不是模拟重写的方法,而是将System.Object作为输入的方法。

    细读执行 EqualityComparer<T>.Default 在Reflector中,似乎有一个特殊的类型实现案例 IEquatable<T> 因此,如果将该接口添加到类中(它已经有了适当的方法),它的行为可能会有所不同。

        2
  •  1
  •   Anderson Imes    15 年前

    马克·西曼是对的。你要确保你暗示的是MOQ在正确的超载:

    mockSomeClass.Setup(c => c.Equals((object)mockSomeClass.Object)).Return(true);
    
        3
  •  1
  •   Steve Freeman    15 年前

    创建一个 SomeClass 是吗?仅仅使用真实的物体会更有意义吗?如果可能的话,这会更好,因为它不会改变容器和类之间关系中的特定行为。

    或者,您可以遍历返回的集合,只需查找与模拟实例相同的对象。这样就避免了任何专门的行为。

        4
  •  0
  •   Robert Koritnik    15 年前

    创建接口 ISomeClass 并使容器使用接口。这样你就可以实现两件事:

    1. 你很容易嘲笑它 Mock<ISomeClass> 实际上,只需对容器的功能进行单元测试
    2. 通过将单元测试容器与测试SomeClass类功能的实际实现分开来更好地进行单元测试。