当使用Moq模拟一个被称为很多次的依赖时,我遇到了麻烦。当我拨打Verify
时,Moq需要很长时间(几分钟)来回复,有时会遇到NullReferenceException
(我想这是可以理解的,考虑到Moq
会有多少数据必须积累来从“冷启动”中做Verify
。)
所以我的问题是,是否有另一种策略我可以使用Moq来做这件事,或者我应该为这个相当不寻常的案例恢复手工制作的存根。具体来说,有没有办法告诉Moq我只对验证参数的特定过滤器以及忽略所有其他值感兴趣?
以下两种方法都不令人满意。
给定CUT和Dep :
public interface ISomeInterface
{
void SomeMethod(int someValue);
}
public class ClassUnderTest
{
private readonly ISomeInterface _dep;
public ClassUnderTest(ISomeInterface dep)
{
_dep = dep;
}
public void DoWork()
{
for (var i = 0; i < 1000000; i++) // Large number of calls to dep
{
_dep.SomeMethod(i);
}
}
}
Moq策略1 - 验证
var mockSF = new Mock<ISomeInterface>();
var cut = new ClassUnderTest(mockSF.Object);
cut.DoWork();
mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)),
Times.Once());
mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)),
Times.Never());
Moq策略2 - 回拨
var mockSF = new Mock<ISomeInterface>();
var cut = new ClassUnderTest(mockSF.Object);
bool isGoodValueAlreadyUsed = false;
mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)))
.Callback(() =>
{
if (isGoodValueAlreadyUsed)
{
throw new InvalidOperationException();
}
isGoodValueAlreadyUsed = true;
});
mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)))
.Callback(() =>
{ throw new InvalidOperationException(); });
cut.DoWork();
Assert.IsTrue(isGoodValueAlreadyUsed);
答案 0 :(得分:3)
通常当达到这样的限制时,我会重新考虑我的设计(没有冒犯,我看到你的代表)。看起来被测试的方法做了太多工作,这违反了单一责任原则。它首先生成一个大的项目列表,然后验证为每个项目调用一个worker,同时还验证该序列包含正确的元素。
我将功能拆分为序列生成器,并验证序列是否具有正确的元素,以及另一个作用于序列的方法,并验证它是否为每个元素执行了worker:
namespace StackOverflowExample.Moq
{
public interface ISequenceGenerator
{
IEnumerable<int> GetSequence();
}
public class SequenceGenrator : ISequenceGenerator
{
public IEnumerable<int> GetSequence()
{
var list = new List<int>();
for (var i = 0; i < 1000000; i++) // Large number of calls to dep
{
list.Add(i);
}
return list;
}
}
public interface ISomeInterface
{
void SomeMethod(int someValue);
}
public class ClassUnderTest
{
private readonly ISequenceGenerator _generator;
private readonly ISomeInterface _dep;
public ClassUnderTest(ISomeInterface dep, ISequenceGenerator generator)
{
_dep = dep;
_generator = generator;
}
public void DoWork()
{
foreach (var i in _generator.GetSequence())
{
_dep.SomeMethod(i);
}
}
}
[TestFixture]
public class LargeSequence
{
[Test]
public void SequenceGenerator_should_()
{
//arrange
var generator = new SequenceGenrator();
//act
var list = generator.GetSequence();
//assert
list.Should().Not.Contain(-1);
Executing.This(() => list.Single(i => i == 12345)).Should().NotThrow();
//any other assertions
}
[Test]
public void DoWork_should_perform_action_on_each_element_from_generator()
{
//arrange
var items = new List<int> {1, 2, 3}; //can use autofixture to generate random lists
var generator = Mock.Of<ISequenceGenerator>(g => g.GetSequence() == items);
var mockSF = new Mock<ISomeInterface>();
var classUnderTest = new ClassUnderTest(mockSF.Object, generator);
//act
classUnderTest.DoWork();
//assert
foreach (var item in items)
{
mockSF.Verify(c=>c.SomeMethod(item), Times.Once());
}
}
}
}
修改强>
可以混合不同的方法来定义特定的期望,包括。 When()
,已过时的AtMost()
,MockBehavior.Strict
,Callback
等。
同样,Moq不适用于大型集合,因此存在性能损失。您还可以使用其他方法来验证将哪些数据传递给模拟器。
对于OP中的示例,这是一个简化的设置:
var mockSF = new Mock<ISomeInterface>(MockBehavior.Strict);
var cnt = 0;
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i != -1)));
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i == 12345))).Callback(() =>cnt++).AtMostOnce();
对于多于一次的12次调用,这将抛出-1,并且可以在cnt != 0
上进行断言。