【问题标题】:Mock behavior loose for single method or generic It.IsAny单一方法或泛型 It.IsAny 的模拟行为松散
【发布时间】:2019-07-25 19:03:53
【问题描述】:

我有一个抽象类Shape 和几个继承自它的类(SquareRectangleTriangleCircle,...)。我还有一个AreaCalculator 类,其中包含一个名为CalculateAreasByShape() 的方法。此方法为每个Shape 类型调用一系列CalculateShapeAreas<TShape>(List<TShape> shapes) 方法。

现在,在我的单元测试中,我有以下代码:

...
var areaMock = new Mock<AreaCalculator>(MockBehavior.Strict);
areaMock.Setup(m => m.CalculateAreasByShape()).Returns(new Dictionary<Type, decimal>());
var canvas = new DemoCanvas(shapes, perimeterMock.Object, areaMock.object);
var result = canvas.RunShapeCalculations();
...

此代码失败的事实是,MockBehavior 设置为 Strict 并且没有设置 CalculateShapeAreas&lt;TShape&gt;() 方法。现在,我知道我可以为每种 Shape 类型运行设置,例如:

areaMock.Setup(m => m.CalculateShapeAreas<Triangle>(It.IsAny<List<Triangle>>())).Returns(default(decimal));

但这会产生很多重复的代码。我想知道我们是否可以使用所有形状都继承自同一个基类的事实。我尝试了以下方法,但它不起作用:

areaMock.Setup(m => m.CalculateShapeAreas<Shape>(It.IsAny<List<Shape>>())).Returns(default(decimal));

或者,是否可以在单个泛型方法上设置MockBehavior.Loose(也就是说,它不会检查实际类型)?

【问题讨论】:

  • 你为什么不模拟接口而不是类呢?喜欢; new Mock&lt;IAreaCalculator&gt;?
  • 因为在测试的后期我还断言AreaCalculator 中不同方法的调用次数,它们相互调用。我跳过了一些设置,以避免在我的问题中出现不必要的代码。
  • AFAIK,您需要摆脱 MockBehavior.Strict 才能做到这一点,然后该类应标记为虚拟。我一直担心您的代码库的简单性,这就是为什么要求提供接口。编码愉快。
  • 类似于Mocking generic methods in Moq without specifying T中描述的问题。就像 Moq 具有用于常用参数的 It.IsAny&lt;&gt; 魔法一样,如果它具有用于类型参数的 Moq.AnyType 那就太好了。然后你可以这样做,例如mock.Setup(pa =&gt; pa.Reserve&lt;Moq.AnyType&gt;()).Returns(...)。在您的示例中,areaMock.Setup(m =&gt; m.CalculateShapeAreas&lt;Moq.AnyType&gt;(It.IsAny&lt;List&lt;Moq.AnyType&gt;&gt;())).Returns(default(decimal)); 实际上可以推断出第一个Moq.AnyType。但是如果类型需要约束呢?
  • @JeppeStigNielsen 最终会是github.com/moq/moq4/issues/343

标签: c# unit-testing moq


【解决方案1】:

It.IsAny 用于开放通用支持尚未出现在 moq(版本 4.12.0)中。最终有一些计划介绍这个,看看here


我认为您面临的问题是因为您使用List&lt;T&gt; 作为输入参数。正如您可能注意到的那样,List&lt;T&gt; 不是协变的。

这是什么意思?

List<Shape> shapeList;
IEnumerable<Shape> shapeEnumerable;
var triag = new List<Triangle>();

shapeList = triag; // NOT ALLOWED
shapeEnumerable = triag; // ALLOWED

模拟时同样会出现问题。一种解决方案可能是更改interface,而不是通过List&lt;T&gt;,而是通过IEnumerable&lt;out T&gt;,或者@JeppeStigNielsen 指出IReadOnlyList&lt;out T&gt;。事实上,T 接口中任何由List&lt;T&gt; 实现的协变量都可以使用。

areaMock
    .Setup(m => m.CalculateShapeAreas<Shape>(It.IsAny<IEnumerable<Shape>>()))
    .Returns(default(decimal));

【讨论】:

  • 如果需要,您还可以使用IReadOnlyList&lt;out T&gt;,它在T 中也是协变的。 T[]List&lt;T&gt; 都实现了它。这样,您可以使用诸如shapes.Count 属性和索引器的get 部分shapes[i] 之类的东西。
  • @JeppeStigNielsen 确实,tnx
  • 在这种情况下,您指向github.com/moq/moq4/issues/343 的链接对我来说是正确答案。拜托,你能把它也添加到你的帖子中吗?
猜你喜欢
  • 2013-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-04
  • 2012-03-07
  • 2011-01-20
  • 2019-04-10
相关资源
最近更新 更多