如何嘲笑'对象.等于(对象 obj)' 表示使用 Moq 的接口

本文关键字:对象 Moq 表示 接口 何嘲笑 等于 obj | 更新日期: 2023-09-27 18:34:40

我有一个有趣的问题要问你。考虑一些像这样的接口:

public interface IMyThing
{
    int Id { get; }
}

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

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

我正在使用Moq模拟所有实现IMyThing的对象:

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>不是为了这个目的
  • 我不想玷污我的模型只是为了让它被嘲笑

你会如何解决这个问题?

如何嘲笑'对象.等于(对象 obj)' 表示使用 Moq 的接口

我最终为 Github 上的Moq项目贡献了代码(请参阅问题 #248(。通过这些更改,可以模拟object.Equals(object obj)object.GetHashCode()object.ToString(),甚至对于接口模拟。

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

我认为问题来自这样一个事实,即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 的测试中使用它,并从中派生出真正的实现。

如果执行比较的方式可以更改,则不应使用直接比较,而应使用可以为您进行比较的对象。

请考虑像这样对类进行更改:

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);
    }
}

然后你只需要嘲笑比较器。

也许我不完全理解你,但我没有看到IMyThing的扩展或覆盖Equals()函数?所以我想我的第一个方法是覆盖或扩展函数,如果它不起作用,我会IMyThing : IEquatable<IMyThing>,我知道IEquatable<T>不是为了这个目的,但它关闭了。最后一个,我认为你不需要这样做,但它存在,只需创建一个像bool IsEquals(IMyThing other){//check if equals}这样的函数