1

我很难提取任何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;使用最小起订量;使用 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; }
}

}

4

1 回答 1

1

至少对我而言,最容易理解Passes2和测试之间的区别是在测试失败的情况下,表达式链中的一个中断。Fails

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

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

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

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

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

在 的情况下Fails,在这条线上...

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

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

在运行时,argumentMatcher可能被评估为null. Moq 看到 parameter-expression-sub-tree 是一个值而不是 a Func,执行与该值的比较,being null,而不是1根据需要执行比较。

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

于 2013-09-20T15:29:01.057 回答