为什么我的参数匹配器在将其提取到变量时会失败?

时间:2013-03-22 17:20:17

标签: c# moq

我很难提取任何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的静态方法。

问题在于,当我期望使用包含3个项目的列表调用它时,Moq说Produce()被调用了空列表。在此示例中,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; 使用System.Linq.Expressions; 使用Moq; 使用Xunit;

命名空间MoqArgumentMatcher {     课程     {         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; }
}

}

1 个答案:

答案 0 :(得分:1)

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

首先要注意的是It.Is的签名:

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

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

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

在Passes和Passes2的情况下,它能够提取Produce。编译器知道应该将代码解析为表达式,因此为Expression<Func<Report, bool>>调用创建表达式树。

It.Is的情况下,在这一行......

Fails

...编译器看到对var argumentMatcher = It.Is<Report>(xx => xx.Id == 1); 的调用,一旦代码运行就会对其进行评估。因此,它确定It.Is的类型将是var(返回类型),而不是任何TValue。因此,当在Expression调用中看到argumentMatcher时,它现在是表达式树中的叶节点,一个简单的变量。

在运行时,Verify可能会被评估为argumentMatcher。 Moq,看到参数表达式子树是一个值而不是null,执行与值Func的比较,而不是执行与null的比较为期望的。

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