如何使用Moq模拟接口的`object.Equals(object obj)`

时间:2016-02-15 10:05:06

标签: c# .net moq

我有一个有趣的问题来包裹你。考虑一下这样的界面:

public interface IMyThing
{
    int Id { get; }
}

现在我想测试使用此接口的代码。也许有一些LINQ魔术。像这样:

public class SomeClass
{
    private IMyThing _thing;

    ...

    public bool HasThing(IEnumerable<IMyThing> things)
    {
        return things.Contains(_thing);
    }
}

我正在使用IMyThing

模拟实现Moq的所有对象
public static IMyThing MockMyThing(int newId)
{
    var mock = new Mock<IMyThing>();
    mock.Setup(s => s.Id).Returns(newId);
    mock.Setup(s => s.Equals(It.IsAny<object>())).Returns<object>(obj =>
    {
        if (typeof(IMyThing).IsAssignableFrom(obj.GetType()))
        {
            return ((IMyThing)obj).Id == newId;
        }

        return false;
    });

    return mock.Object;
}

这是事情。上面的代码编译没有警告,但永远不会工作。 MoqEquals()方法创建一个拦截器,但永远不会到达。而是调用对象代理的equals方法。我指责的是我在嘲笑一个界面而不是一个具体的类。

更新:刚刚意识到Moq甚至没有创建拦截器。

当然,我可以像这样扩充IMyThing界面:

public interface IMyThing : IEquatable<IMyThing>
{
    int Id { get; }
}

LINQ运算符将识别IEquatable<T>接口并使用它。

我不想这样做是因为:

  • 这仅适用于其他IMyThing个对象
  • IEquatable<T>并非出于此目的
  • 我不想让我的模特变得模糊不清

你会如何解决这个问题?

4 个答案:

答案 0 :(得分:6)

我最终为Github上的Moq项目提供了代码(参见issue #248)。通过此更改,即使对于界面模拟,也可以模拟object.Equals(object obj)object.GetHashCode()object.ToString()

让我们看看它是否被接受。

答案 1 :(得分:1)

我认为问题来自于Equals方法不在您正在嘲笑的界面上。如果您使用可覆盖的Equals方法创建一个类(即使它什么都不做),那么您可以模拟它。

public class MyTestThing : IMyThing
{
    public virtual int Id { get; }

    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }
}

[TestCase(55)]
public void Can_mock_equals_method(int newId)
{
    var mockThing = new Mock<MyTestThing>();

    mockThing.Setup(t => t.Id).Returns(newId);
    mockThing.Setup(t => t.Equals(It.IsAny<object>()))
        .Returns<object>(t => (t as IMyThing)?.Id == newId);

    Assert.That(mockThing.Object.Equals(new MyRealThing(newId)));
}

请注意,如果您在Equals上注释掉MyTestThing方法,则此测试将失败,因为Moq无法再模拟它。

如果您要创建这样的测试类,那么在类本身中实际完全实现Equals可能更有用,因此您不必费心使用Moq进行设置。您甚至可以更进一步创建一个抽象基类,其中唯一实现的方法是Equals,并在您使用Moq的测试中使用它,并从中派生您的实际实现。

答案 2 :(得分:1)

如果您执行比较的方式可能会发生变化,您不应该使用直接比较,而是使用可以为您进行比较的对象。

考虑对您的班级进行更改:

public class SomeClass
{
    private IMyThing _thing;

    public bool HasThing(IEnumerable<IMyThing> things, IEqualityComparer<IMyThing> comparer = null)
    {
        comparer = comparer ?? EqualityComparer<IMyThing>.Default;
        return things.Contains(_thing, comparer);
    }
}

然后你也必须嘲笑比较器。

答案 3 :(得分:0)

也许我完全不理解你,但是我没有看到Equals()的{​​{1}}功能? 所以我想我的第一种方法将是overide或extandes功能, 如果它不起作用我会IMyThing,我知道IMyThing : IEquatable<IMyThing>不是为了这个目的,而是关闭。 而最后我不认为你需要这样做但它存在,只需创建一个像IEquatable<T>

这样的函数