使用Lambda表达式验证方法调用 - Moq

25

我有一个工作单元实现,其中包括以下方法:

T Single<T>(Expression<Func<T, bool>> expression) where T : class, new();

例如,我将其称为:

var person = _uow.Single<Person>(p => p.FirstName == "Sergi");

如何验证使用FirstName == "Sergi"的参数调用了Single方法?

我尝试过以下方法,但没有成功:

// direct approach 
session.Verify(x => x.Single<Person>(p => p.FirstName == "Sergi"));

// comparing expressions
Expression<Func<Person, bool>> expression = p => p.FirstName == "Sergi");

session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => e == expression));

它们都会导致以下错误:

期望在模拟对象上至少调用一次,但从未执行

有什么想法吗? 我正在使用最新的Moq版本4.0.10827.0来自NuGet。

更新:具体示例

我发现只要在lambda内部使用字符串字面量,Verify就工作正常。 但是一旦我比较变量,它就会失败。 例如:

// the verify
someService.GetFromType(QuestionnaireType.Objective)

session.Verify(x => x.Single<Questionnaire>(q => 
    q.Type == QuestionnaireType.Objective));


// QuestionnaireType.Objective is just a constant:
const string Objective = "objective";


// the method where it's called (FAILS):
public Questionnaire GetFromType(string type)
{
    // this will fail the Verify
    var questionnaire = _session
        .Single<Questionnaire>(q => q.Type == type);
}

// the method where it's called (PASSES):
public Questionnaire GetFromType(string type)
{
    // this will pass the Verify
    var questionnaire = _session
        .Single<Questionnaire>(q => q.Type == QuestionnaireType.Objective);
}

当我在lambda表达式中使用方法参数时,为什么Verify会立即失败?

写这个测试的正确方法是什么?

2个回答

18

对我来说,直接的方法很有效:

// direct approach 
session.Verify(x => x.Single<Person>(p => p.FirstName == "Sergi"));

表达式对象不能对等价表达式返回true,因此这将失败:

// comparing expressions
Expression<Func<Person, bool>> expression = p => p.FirstName == "Sergi");

session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => e == expression));

要理解原因,请运行以下NUnit测试:

[Test]
public void OperatorEqualEqualVerification()
{
    Expression<Func<Person, bool>> expr1 = p => p.FirstName == "Sergi";
    Expression<Func<Person, bool>> expr2 = p => p.FirstName == "Sergi";
    Assert.IsTrue(expr1.ToString() == expr2.ToString());
    Assert.IsFalse(expr1.Equals(expr2));
    Assert.IsFalse(expr1 == expr2);
    Assert.IsFalse(expr1.Body == expr2.Body);
    Assert.IsFalse(expr1.Body.Equals(expr2.Body));
}

正如上面的测试所示,按表达式主体进行比较也会失败,但字符串比较是可行的,因此这个方法也可以奏效:

// even their string representations!
session.Verify(x => x
    .Single(It.Is<Expression<Func<Person, bool>>>(e => 
        e.ToString() == expression.ToString()));

这里还有一种测试样式,可以添加到工具库中并可行:

[Test]
public void CallbackVerification()
{
    Expression<Func<Person, bool>> actualExpression = null;
    var mockUow = new Mock<IUnitOfWork>();
    mockUow
        .Setup(u => u.Single<Person>(It.IsAny<Expression<Func<Person, bool>>>()))
        .Callback( (Expression<Func<Person,bool>> x) => actualExpression = x);
    var uow = mockUow.Object;
    uow.Single<Person>(p => p.FirstName == "Sergi");

    Expression<Func<Person, bool>> expectedExpression = p => p.FirstName == "Sergi";

    Assert.AreEqual(expectedExpression.ToString(), actualExpression.ToString());
}

由于您有许多不该失败的测试用例失败了,所以您很可能有不同的问题。

更新:根据您的更新,请考虑以下设置和表达式:

string normal_type = "NORMAL";
// PersonConstants is a static class with NORMAL_TYPE defined as follows:
// public const string NORMAL_TYPE = "NORMAL";
Expression<Func<Person, bool>> expr1 = p => p.Type == normal_type;
Expression<Func<Person, bool>> expr2 = p => p.Type == PersonConstants.NORMAL_TYPE;

一个表达式引用了包含方法的实例变量。另一个表示引用静态类的常量成员的表达式。这两个是不同的表达式,不管在运行时分配给变量的值是什么。然而,如果将string normal_type更改为const string normal_type,则两个表达式再次相同,因为它们都引用了表达式右侧的const


非常感谢你的回答。根据新的嗯……发现,我已经更新了我的问题,为了更好的表达。有什么想法吗? - Sergi Papaseit
1
我已经更新了我的问题,以解决可能是您的问题。这实际上取决于“QuestionnaireType.Objective”的类型。我预计如果您对这两个表达式进行ToString()操作,您会发现它们是不同类型的。 - Kaleb Pederson
非常感谢,我想这是有道理的。但是那么该如何编写这个测试才是正确的呢?我觉得在这种情况下,模拟反而会成为更清晰代码的障碍,而不是推进器。 - Sergi Papaseit
+1 给“直接方法”。它能够工作并且似乎是最简单的解决方案(通常也是正确的)。 - geekzster
+1 提醒要使用 Callback(),当您需要测试私有辅助方法所返回的值对输入参数的影响时。 - gabe

1
我还想分享另一种比较参数表达式和预期表达式的方法。 我在StackOverflow上搜索“如何比较表达式”,然后找到了这些文章:

然后我找到了这个Subversion存储库,里面有db4o.net。 在他们的一个项目中,命名空间Db4objects.Db4o.Linq.Expressions,他们包括一个名为ExpressionEqualityComparer的类。 我能够从存储库中检出此项目,编译、构建并创建DLL以在自己的项目中使用。

使用ExpressionEqualityComparer,您可以将Verify调用修改为以下内容:

session.Verify(x => x .Single(It.Is<Expression<Func<Person, bool>>>(e => new ExpressionEqualityComparer().Equals(e, expression))));

最终,在这种情况下,ExpressionEqualityComparerToString()技术都返回true(ToString可能更快-速度未经测试)。个人而言,我更喜欢比较器方法,因为我觉得它更具自我记录性并更好地反映了您的设计意图(比较表达式对象而不是其ToString输出的字符串比较)。

注意:我仍在寻找此项目中的db4o.net许可文件,但我没有以任何方式修改代码,包括版权声明,并且(由于页面是公开可用的),我假设现在足够了...;-)


我个人认为MOQ应该添加ExpressionComparer,并在使用Setup和Verify时使用它。 - Robetto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接