为什么我的参数匹配器失败时,我提取到一个变量
本文关键字:提取 变量 一个 参数 我的 失败 为什么 | 更新日期: 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; }
}
}
Passes2
和Fails
测试之间的区别是最容易理解的,至少对我来说,在测试失败的情况下,作为表达式链中的中断。
It.Is
的签名:
TValue It.Is<TValue>(Expression<Func<TValue, bool>> match)
特别要注意的是,当它执行时,它返回一个TValue
的实例,而不是Expression
。接下来要注意的是Verify
的签名需要一个Expression
(类型为Action
或Func
),其中需要调用所需的方法。
当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
。因此,当argumentMatcher
在Verify
调用中出现时,它现在是表达式树中的一个叶节点,一个简单的变量。
在运行时,argumentMatcher
可能被求值为null
。Moq看到参数表达式子树是一个值,而不是一个Func
,就对null
的值执行比较,而不是像期望的那样对1
执行比较。
(这是在回答开放式问题的精神,尽管OP对另一个问题的答案感到满意!)