.net c#当我将参数匹配器提取到变量中时,为什么它会失败?

Why does my argument matcher fail when I extract it into a variable? [duplicate]
2021-09-14
  •  译文(汉语)
  •  原文(英语)

我很难提取任何It.Is<T>参数匹配器变量.每当我这样做时,测试都会失败.

这有效:

calculatorMock
    .Setup(x => x.Produce(It.Is<IEnumerable<Report>>(xx => reports.IsEqualTo(xx))))
    .Returns(calculatorInputs);

但是,这失败了:

var argumentMatcher = It.Is<IEnumerable<Report>>(xx => reports.IsEqualTo(xx));
calculatorMock
    .Setup(x => x.Produce(argumentMatcher))
    .Returns(calculatorInputs);

IsEqualTo 是返回布尔值的静态方法.

问题是,Produce()当我希望使用包含3个项目的列表调用Moq时,Moq表示使用空列表进行了调用.在此示例中,xx表示空列表.我不确定为什么我需要Moq验证内联参数匹配器.

我刚刚发现以下作品:

Expression<Func<IEnumerable<Report>, bool>> expression = x => reports.IsEqualTo(x);
calculatorMock
    .Setup(x => x.Produce(It.Is(expression)))
    .Returns(calculatorInputs);

是否有特定原因为什么It.Is<T>无法像我上面尝试的那样提取?

这是问题的工作副本:

使用系统;使用System.Linq.Expressions; 使用最小起订量; 使用Xunit;

命名空间MoqArgumentMatcher {类Program {静态void Main(string [] args){var testRunner = new TestRunner();

        testRunner.Passes();
        testRunner.Fails();

        Console.ReadKey();
    }
}

public class TestRunner
{
    [Fact]
    public void Passes()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report {Id = 1};

        // Act
        consumer.Consume(report);

        // Assert
        calculatorMock.Verify(x => x.Produce(
            It.Is<Report>(xx => xx.Id == 1)), Times.Once());
    }

    [Fact]
    public void Passes2()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report { Id = 1 };

        // Act
        consumer.Consume(report);

        // Assert
        Expression<Func<Report, bool>> expression = x => x.Id == 1;
        calculatorMock.Verify(x => x.Produce(It.Is(expression)), Times.Once());
    }

    [Fact]
    public void Fails()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report {Id = 1};

        // Act
        consumer.Consume(report);

        // Assert
        var argumentMatcher = It.Is<Report>(xx => xx.Id == 1);
        calculatorMock.Verify(x => x.Produce(argumentMatcher), Times.Once());
    }
}

public class CalculatorConsumer
{
    private readonly ICalculator _calculator;

    public CalculatorConsumer(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public void Consume(Report report)
    {
        _calculator.Produce(report);
    }
}

public interface ICalculator
{
    void Produce(Report report);
}

public class Report
{
    public int Id { get; set; }
}

}

速聊1:
"此失败"的描述太含糊了.它怎么会失败?
速聊2:
: 你是绝对正确的.我添加了所有我能想到的.
速聊3:
您可以在简短但完整的程序中重现此内容吗?这将使测试变得更加容易.我怀疑这与类型推断有关,但是很难确定.(Moq文档链接在项目页面上不起作用无济于事!)
速聊4:
:感谢您的建议答案.这似乎正是我所遇到的.
解决过程1

至少对我而言,Passes2Fails测试之间的区别最容易理解为,如果测试失败,则表达链中断.

首先要注意的是It.Is:

TValue It.Is<TValue>(Expression<Func<TValue, bool>> match)

特别要注意的是,执行该命令时,它会返回的实例TValue,而不是的实例Expression.接下来要注意的是VerifyExpects的签名Expression(类型ActionFunc),其中将调用所需的方法.

Moq执行该Verify方法时,它会查看表达式并提取要验证的方法调用,然后表达式中为正在调用的方法提供值的部分,在本例中为中的report参数Produce(Report report).然后,它将编译此小的parameter-expression-sub-tree以针对用于调用该Produce方法以确定其是否匹配的值执行.

在Passes和Passes2的情况下,它可以提取Expression<Func<Report, bool>>.编译器知道应该将代码解析为表达式,因此为It.Is调用创建了一个表达式树.

在的情况下Fails,在此行...

var argumentMatcher = It.Is<Report>(xx => xx.Id == 1);

...编译器It.Is会在代码运行后立即看到对该调用的评估.因此,它确定的类型var将是TValue(返回类型),而不是Expression任何东西.因此,当argumentMatcherVerify调用内部看到它时,它现在是表达式树中的叶节点,这是一个简单的变量.

在运行时,argumentMatcher可能评估为null.Moq看到parameter-expression-sub-tree是一个值而不是a Func,则对值asnull进行比较,而不是1根据需要执行比较.

(尽管OP对另一个问题的回答感到满意,这是本着回答开放性问题的精神!)

速聊1:
这是一个了不起的答案!非常感谢您提供了令人难以置信的深刻答案.

I'm having a hard time extracting any It.Is<T> argument matchers variables. Whenever I do so the test fails.

This works:

calculatorMock
    .Setup(x => x.Produce(It.Is<IEnumerable<Report>>(xx => reports.IsEqualTo(xx))))
    .Returns(calculatorInputs);

However, this fails:

var argumentMatcher = It.Is<IEnumerable<Report>>(xx => reports.IsEqualTo(xx));
calculatorMock
    .Setup(x => x.Produce(argumentMatcher))
    .Returns(calculatorInputs);

IsEqualTo is a static method returning bool.

The issue is that Moq says Produce() was invoked with a null list when I'm expecting it to be called with a list containing 3 items. In this example, xx denotes the null list. I'm not sure why I need the argument matcher inline with my Moq verification.

I just found out that the following works:

Expression<Func<IEnumerable<Report>, bool>> expression = x => reports.IsEqualTo(x);
calculatorMock
    .Setup(x => x.Produce(It.Is(expression)))
    .Returns(calculatorInputs);

Is there a specific reason why It.Is<T> cannot be extracted like I attempted to do above?

Here is a working copy of the problem:

using System; using System.Linq.Expressions; using Moq; using Xunit;

namespace MoqArgumentMatcher { class Program { static void Main(string[] args) { var testRunner = new TestRunner();

        testRunner.Passes();
        testRunner.Fails();

        Console.ReadKey();
    }
}

public class TestRunner
{
    [Fact]
    public void Passes()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report {Id = 1};

        // Act
        consumer.Consume(report);

        // Assert
        calculatorMock.Verify(x => x.Produce(
            It.Is<Report>(xx => xx.Id == 1)), Times.Once());
    }

    [Fact]
    public void Passes2()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report { Id = 1 };

        // Act
        consumer.Consume(report);

        // Assert
        Expression<Func<Report, bool>> expression = x => x.Id == 1;
        calculatorMock.Verify(x => x.Produce(It.Is(expression)), Times.Once());
    }

    [Fact]
    public void Fails()
    {
        // Arrange
        var calculatorMock = new Mock<ICalculator>();
        var consumer = new CalculatorConsumer(calculatorMock.Object);
        var report = new Report {Id = 1};

        // Act
        consumer.Consume(report);

        // Assert
        var argumentMatcher = It.Is<Report>(xx => xx.Id == 1);
        calculatorMock.Verify(x => x.Produce(argumentMatcher), Times.Once());
    }
}

public class CalculatorConsumer
{
    private readonly ICalculator _calculator;

    public CalculatorConsumer(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public void Consume(Report report)
    {
        _calculator.Produce(report);
    }
}

public interface ICalculator
{
    void Produce(Report report);
}

public class Report
{
    public int Id { get; set; }
}

}

Talk1:
"this fails" is far too vague a description. How does it fail?
Talk2:
: You're absolutely right. I've added everything I can think of.
Talk3:
Can you reproduce this in a short but complete program? That would make it easier to test. I suspect it's to do with type inference, but it's hard to say for sure. (It doesn't help that the Moq documentation link doesn't work on the project page!)
Talk4:
: Thanks for the suggested answer. It appears to be exactly what I'm running into.
Solutions1

The difference between the Passes2 and Fails tests is most easily understood, to me at least, as a break in the Expression chain in the case of the failing test.

The first thing to note is the signature of It.Is:

TValue It.Is<TValue>(Expression<Func<TValue, bool>> match)

In particular, note that when it is executed, it returns an instance of TValue, not an Expression. The next thing to note is the signature of Verify expects an Expression (of type Action or Func) in which one is to call the required method.

When Moq executes the Verify method, it looks at the expression and extracts the method call it is verifying then the parts of the expression that are supplying values for the method being called, in this case the report argument in Produce(Report report). It then compiles this small parameter-expression-sub-tree to execute against the value that was used to call the Produce method to determine if it's a match.

In the case of the Passes and Passes2, it is able to extract an Expression<Func<Report, bool>>. The compiler knows that it is supposed to be parsing the code into an expression so creates an expression tree for the It.Is call.

In the case of Fails, on this line...

var argumentMatcher = It.Is<Report>(xx => xx.Id == 1);

...the compiler sees a call to It.Is that is going to be evaluated as soon as the code is run. It therefore determines that the type of var is going to be TValue (the return type), not an Expression of anything. Thus when argumentMatcher is seen inside the Verify call it is now a leaf node in the expression tree, a simple variable.

At run time, argumentMatcher is probably evaluated as null. Moq, seeing that the parameter-expression-sub-tree is a value and not a Func, performs a comparison against the value, being null, rather than executing the comparison against 1 as desired.

(This is in the spirit of answering open questions despite the OP being satisfied with an answer on another question!)

Talk1:
This is an awesome answer! Thank you very much for providing an incredibly deep answer.
转载于:https://stackoverflow.com/questions/15576329/why-does-my-argument-matcher-fail-when-i-extract-it-into-a-variable

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入