为什么我的参数匹配器失败时,我提取到一个变量

本文关键字:提取 变量 一个 参数 我的 失败 为什么 | 更新日期: 2023-09-27 18:05:16

我很难提取任何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是一个返回bool值的静态方法。

问题是,Moq说Produce()是用一个空列表调用时,我期待它被称为包含3个项目的列表。在本例中,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;使用Moq;使用Xunit;

MoqArgumentMatcher

名称空间{类项目{静态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; }
}

}

为什么我的参数匹配器失败时,我提取到一个变量

Passes2Fails测试之间的区别是最容易理解的,至少对我来说,在测试失败的情况下,作为表达式链中的中断。

首先要注意的是It.Is的签名:
TValue It.Is<TValue>(Expression<Func<TValue, bool>> match)

特别要注意的是,当它执行时,它返回一个TValue的实例,而不是Expression。接下来要注意的是Verify的签名需要一个Expression(类型为ActionFunc),其中需要调用所需的方法。

当Moq执行Verify方法时,它查看表达式并提取方法调用,然后验证表达式中为被调用的方法提供值的部分,在本例中是Produce(Report report)中的report参数。然后,它编译这个小的参数表达式子树,对用于调用Produce方法的值执行,以确定它是否匹配。

在pass和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看到参数表达式子树是一个值,而不是一个Func,就对null的值执行比较,而不是像期望的那样对1执行比较。

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