模拟和验证对包含表达式<func <t,bool>&gt;的方法的调用参数</FUNC <t时,布尔>

时间:2014-01-10 10:53:17

标签: .net unit-testing mocking

我想使用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))

2 个答案:

答案 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 postExpression的比较可能非常脆弱,因为默认是参考,需要寻找替代方案。但是,使用引用的方法,即通过表达式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类型的调用参数列表,就像可以在VerifySetup中使用的任何其他类型的参数一样< / p>

修改

上述适用于表达式中没有外部或闭包变量的简单情况,因此两个表达式都等效地序列化。来自@Oliver的引用相等性将适用于CUT将相同的表达式实例传递给依赖项的情况。但在一般情况下,你会以某种方式确定2个表达式是否相等,例如使用一组已知的输入和输出来调用这两个表达式(但这更多地与表达式/函数/ Lambda的等价性问题有关 - 而不是真正的Moq问题)。