如何使用 Moq 为接口模拟 `object.Equals(object obj)`
How to mock `object.Equals(object obj)` for an interface using Moq
我有一个有趣的问题需要解决。考虑一些像这样的界面:
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;
}
事情是这样的。上面的代码在没有警告的情况下编译,但永远不会工作。 Moq
为 Equals()
方法创建了一个拦截器,但它从未被访问过。相反,调用对象代理的 equals 方法。我在指责我在模拟一个接口而不是一个具体的 class.
更新: 刚刚意识到 Moq
甚至没有创建拦截器。
当然我可以像这样扩充 IMyThing
界面:
public interface IMyThing : IEquatable<IMyThing>
{
int Id { get; }
}
LINQ 运算符将识别 IEquatable<T>
接口并使用它。
我不想这样做,因为:
- 这仅适用于其他
IMyThing
个对象
IEquatable<T>
并非用于此目的
- 我不想污染我的模型只是为了让它可以模拟
你会如何解决这个问题?
也许我不完全理解你,但我没有看到 IMyThing
的扩展或覆盖 Equals()
功能?
所以我想我的第一种方法将被覆盖或扩展功能,
如果它不起作用,我会 IMyThing : IEquatable<IMyThing>
,我知道 IEquatable<T>
不是为了这个目的,但它关闭了。
最后一个,我认为你不需要这样做,但它存在,只需创建一个像 bool IsEquals(IMyThing other){//check if equals}
这样的函数
我认为问题出在 Equals
方法不在您正在模拟的接口上。如果您使用可覆盖的 Equals
方法创建 class(即使它什么都不做),那么您就可以模拟它。
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)));
}
请注意,如果您在 MyTestThing
上注释掉 Equals
方法,此测试将失败,因为 Moq 无法再模拟它。
如果您打算像这样创建测试 classes,那么在 class 本身中完全实现 Equals
可能更有用,这样您就不必麻烦用最小起订量设置它。您甚至可以更进一步,创建一个抽象基础 class,其中唯一实现的方法是 Equals
,并在您的 Moq 测试中使用它,并从中派生出您的实际实现。
如果执行比较的方式可以改变,则不应使用直接比较,而应使用可以为您进行比较的对象。
考虑像这样更改您的 class:
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);
}
}
然后你只需要模拟比较器。
我最终为 Github 上的 Moq
项目贡献了代码(参见 issue #248)。通过此更改,可以模拟 object.Equals(object obj)
、object.GetHashCode()
和 object.ToString()
,甚至是接口模拟。
让我们看看它是否被接受。
我有一个有趣的问题需要解决。考虑一些像这样的界面:
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;
}
事情是这样的。上面的代码在没有警告的情况下编译,但永远不会工作。 Moq
为 Equals()
方法创建了一个拦截器,但它从未被访问过。相反,调用对象代理的 equals 方法。我在指责我在模拟一个接口而不是一个具体的 class.
更新: 刚刚意识到 Moq
甚至没有创建拦截器。
当然我可以像这样扩充 IMyThing
界面:
public interface IMyThing : IEquatable<IMyThing>
{
int Id { get; }
}
LINQ 运算符将识别 IEquatable<T>
接口并使用它。
我不想这样做,因为:
- 这仅适用于其他
IMyThing
个对象 IEquatable<T>
并非用于此目的- 我不想污染我的模型只是为了让它可以模拟
你会如何解决这个问题?
也许我不完全理解你,但我没有看到 IMyThing
的扩展或覆盖 Equals()
功能?
所以我想我的第一种方法将被覆盖或扩展功能,
如果它不起作用,我会 IMyThing : IEquatable<IMyThing>
,我知道 IEquatable<T>
不是为了这个目的,但它关闭了。
最后一个,我认为你不需要这样做,但它存在,只需创建一个像 bool IsEquals(IMyThing other){//check if equals}
我认为问题出在 Equals
方法不在您正在模拟的接口上。如果您使用可覆盖的 Equals
方法创建 class(即使它什么都不做),那么您就可以模拟它。
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)));
}
请注意,如果您在 MyTestThing
上注释掉 Equals
方法,此测试将失败,因为 Moq 无法再模拟它。
如果您打算像这样创建测试 classes,那么在 class 本身中完全实现 Equals
可能更有用,这样您就不必麻烦用最小起订量设置它。您甚至可以更进一步,创建一个抽象基础 class,其中唯一实现的方法是 Equals
,并在您的 Moq 测试中使用它,并从中派生出您的实际实现。
如果执行比较的方式可以改变,则不应使用直接比较,而应使用可以为您进行比较的对象。
考虑像这样更改您的 class:
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);
}
}
然后你只需要模拟比较器。
我最终为 Github 上的 Moq
项目贡献了代码(参见 issue #248)。通过此更改,可以模拟 object.Equals(object obj)
、object.GetHashCode()
和 object.ToString()
,甚至是接口模拟。
让我们看看它是否被接受。