我想使用Moq或RhinoMocks来模拟此接口,以验证正确的表达式是作为参数传递的(并且愿意切换到可以支持此功能的任何其他开源模拟库):
完整源代码:
public class Record
{
public int RecordId { get; set; }
}
public interface IRepository
{
void DeleteRecordsByFilter(Expression<Func<Record, bool>> filter);
}
public class MyClass
{
private readonly IRepository _repo;
public MyClass(IRepository repo)
{
_repo = repo;
}
public void DeleteRecords(int recordId)
{
_repo.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId));
}
}
[TestFixture]
public class MyFixture
{
[Test]
public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo()
{
const int recordId = 10;
var repo = new Mock<IRepository>();
repo.Setup(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId)));
var sut = new MyClass(repo.Object);
sut.DeleteRecords(recordId);
repo.Verify(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId)));
}
}
当我执行单元测试时,它失败并显示错误:
Moq.MockException:模拟上的预期调用至少一次, 但从未执行过:method =&gt; method.DeleteRecordsByFilter(x =&gt; x.RecordId.Equals(10))
已配置的设置:method =&gt; method.DeleteRecordsByFilter(x =&gt; x.RecordId.Equals(10)),Times.Never
执行调用:IRepository.DeleteRecordsByFilter(x =&gt; x.RecordId.Equals(值(MyClass的+ LT;&GT; c__DisplayClass0).recordId))
答案 0 :(得分:1)
如果您正在使用Moq,并且不愿意真正检查正确的参数是否传递给您的函数,我想this is what you are looking for:
_mock.Setup(method => method.DeleteRecordsByFilter(It.IsAny<Expression<Func<Record,bool>>>());
(使用RhinoMock与.IgnoreArguments()
等价)
问题是即使两个表达式是“相同的”(逻辑上相同),它们也不是对象的引用,并且System.Linq.Expression没有实现.Equals()覆盖。因此,他们与Moq“不同”。
这就是你得到例外的原因。
如果你想比较你的表达式,你必须在你的模拟上设置一个回调,它将比较两个表达式(例如使用ExprssionVisitor)。
修改强> 的
一个例子,如果按照以下方式实施,您的测试应该有效:
[Test]
public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo()
{
const int recordId = 10;
var repo = new Mock<IRepository>();
Expression<Func<Record,bool>> exp = x => x.RecordId.Equals(recordId);
repo.Setup(method => method.DeleteRecordsByFilter(exp));
var sut = new MyClass(repo.Object);
sut.DeleteRecords(recordId);
repo.Verify(method => method.DeleteRecordsByFilter(exp));
}
但它没有测试任何东西:)
答案 1 :(得分:1)
根据this post,Expression
的比较可能非常脆弱,因为默认是参考,需要寻找替代方案。但是,使用引用的方法,即通过表达式ToString()
的简单Body
比较,您可以像这样验证表达式:
var _mock = new Mock<IInterfaceToBeMocked>();
// "Act" step -> invoke your CUT, which in turn calls the mocked interface dependency
// (Obviously your CUT will do this, but just to prove the point ...)
_mock.Object.DeleteRecordsByFilter(x => x.RecordId.Equals(10));
// Back to the unit test
Expression<Func<Record, bool>> goodComparison = x => x.RecordId.Equals(10);
Expression<Func<Record, bool>> badComparison = x => x.RecordId > 10 && x.RecordId < 12;
_mock.Verify(m => m.DeleteRecordsByFilter(
It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == goodComparison.Body.ToString())),
Times.Once);
_mock.Verify(m => m.DeleteRecordsByFilter(
It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == badComparison.Body.ToString())),
Times.Never());
(即Moq
保留Expression
类型的调用参数列表,就像可以在Verify
或Setup
中使用的任何其他类型的参数一样< / p>
修改的
上述适用于表达式中没有外部或闭包变量的简单情况,因此两个表达式都等效地序列化。来自@Oliver的引用相等性将适用于CUT将相同的表达式实例传递给依赖项的情况。但在一般情况下,你会以某种方式确定2个表达式是否相等,例如使用一组已知的输入和输出来调用这两个表达式(但这更多地与表达式/函数/ Lambda的等价性问题有关 - 而不是真正的Moq问题)。