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

发布于 2024-11-19 08:48:35 字数 1896 浏览 3 评论 0 原文

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

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

例如,我这样调用它:

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

如何验证 Single 方法是否已使用参数 < 调用代码>名字==“Sergi”

我已尝试以下方法,但无济于事:

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

它们都会导致以下错误:

预期对模拟至少调用一次,但从未执行

关于如何做到这一点的任何想法? 我使用的是 NuGet 的最新 Moq,版本 4.0.10827.0

更新:一个具体的例子

我所看到的是,每当我在 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 就会失败?

编写此测试的正确方法是什么?

I have a Unit of Work implementation with, among others, the following method:

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

and I call it, for instance, like this:

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

How can I verify that the Single method has been called with an argument of FirstName == "Sergi"?

I've tried the following, but to no avail:

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

They all result in the folowing error:

Expected invocation on the mock at least once, but was never performed

Any ideas on how that can be done?
I'm using the latest Moq from NuGet, version 4.0.10827.0

UPDATE: A Specific example

What I'm seeing is that whenever I use string literals inside the lambda, Verify works. As soon as I'm comparing variables it fails. Case in point:

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

How come the Verify fails as soon as I use the method parameter in the lambda expression?

What would be the proper way to write this test?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乖乖兔^ω^ 2024-11-26 08:48:35

直接方法对我来说效果很好:

// 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;

一个表达式引用包含方法的实例变量。另一个表示引用静态类的 const 成员的表达式。无论在运行时分配给变量的值如何,两者都是不同的表达式。但是,如果将 string normal_type 更改为 const string normal_type,则表达式再次相同,因为每个引用右侧的 const的表达。

The direct approach works just fine for me:

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

The expression object doesn't return true for equivalent expressions so this will fail:

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

To understand why, run the following NUnit test:

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

And as the test above indicates, comparing by the expression body will also fail, but string comparison works, so this works as well:

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

And here's one more style of test you can add to the arsenal that also works:

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

As you have a number of test cases that fail that shouldn't, you likely have a different problem.

UPDATE: Per your update, consider the following setup and expressions:

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;

One expression references an instance variable of the containing method. The other represents an expression that references a const member of a static class. The two are different expressions, regardless of the values that may be assigned to the variables at runtime. If however, string normal_type is changed to const string normal_type then the expressions are again the same as each reference a const on the right hand side of the expression.

雪落纷纷 2024-11-26 08:48:35

我还想分享另一种将参数表达式与预期表达式进行比较的方法。我在 StackOverflow 中搜索“如何比较表达式”,结果找到了以下文章:

然后我被引导到此 Subversion 存储库对于 db4o.net。在他们的一个项目(命名空间 Db4objects.Db4o.Linq.Expressions)中,它们包含一个名为 ExpressionEqualityComparer 的类。我能够从存储库中检出该项目,编译、构建并创建一个 DLL 以在我自己的项目中使用。

使用 ExpressionEqualityComparer,您可以将 Verify 调用修改为如下所示:

session.Verify(x => x
.Single(It.Is<表达式>>(e =>;
new ExpressionEqualityComparer().Equals(e, expression))));

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

注意:我仍在这个项目中寻找 db4o.net 许可证文件,但我没有修改代码,包括版权声明,并且(因为该页面是公开可用的)我想现在就足够了......;-)

I would also like to share another approach to comparing the parameter expression to the expected expression. I searched StackOverflow for "how to compare expressions," and I was led to these articles:

I was then led to this Subversion repository for db4o.net. In one of their projects, namespace Db4objects.Db4o.Linq.Expressions, they include a class named ExpressionEqualityComparer. I was able to checkout this project from the repository, compile, build, and create a DLL to use in my own project.

With the ExpressionEqualityComparer, you can modify the Verify call to something like the following:

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

Ultimately, the ExpressionEqualityComparer and the ToString() techniques both return true in this case (with the ToString most likely being faster - speed not tested). Personally, I prefer the comparer approach since I feel it is more self-documenting and better reflects your design intent (comparing the expression objects rather a string comparison of their ToString outputs).

Note: I'm still looking for a db4o.net license file in this project, but I've not modified the code in anyway, included the copyright notice, and (since the page is publicly available) I'm assuming that's enough for now... ;-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文