Arg.Do()没有在预期的时候触发.Do for void方法
本文关键字:Do 候触发 void 方法 for Arg | 更新日期: 2023-09-27 18:09:53
在我的测试中有以下结构,旨在测试使用正确的复杂参数对象调用某个日志,即使它抛出异常,然后包装并通常进一步操作。logThing有一个方法:
void AddEntry(LogEntry);
所以我使用When. Do来抛出异常,
public void UnitTest()
{
// Arrange
ILogThing logThing = Substitute.For<ILogThing>()
SystemUnderTest system = new SystemUnderTest(logThing);
List<LogEntry> actualEntries = new List<LogEntry>();
LogEntry expectedEntry = GetSomeTestData();
logThing.When(
lt => lt.AddEntry(Arg.Do<LogEntry>(r => actualEntries.Add(r)))).Do(
call => { throw new InvalidOperationException("testMessage"); });
// Act
try
{
system.DoSomethingWhichLogs(someArgs)
}
catch(WrappedException ex)
{
// Assert
Assert.AreEqual(1, actualEntries.Count);
Assert.AreEqual(actualEntries[0], expectedEntry);
}
}
然而,在这种设置下,预期的对Arg.Do()的调用永远不会发生。
我在catch块中放置了一个断点,并使用Visual Studio的即时窗口在logThing上调用RecievedCalls<>(),并且它确实有对logThing的单个调用的记录,并带有正确的参数-这只是Arg。Do似乎只在When..Do块完成后执行。很明显,这意味着既然我要把时间…做,它永远不会到达它。
我真的没有期望NSubstitute以这种方式排序调用,这是预期的行为吗?如果是这样,我能做些什么来测试传入的参数像这样,或者我应该把我的参数检查到主When. do块(这使得它更难阅读)?
被测系统对异常做了各种各样的事情,包括将它与logEntry包装在一起,所以对我来说,在一个测试中进行所有这些检查是有用的——我确实考虑过将它分成两个单独的测试,但意识到如果我这样做,我不能很容易地确定不正确的包装输出来自哪里(它可能是最初生成logEntry的部分,或包装它的部分),而使用此模式,我可以检查以确保logThing正在接收我所期望的内容。不过,如果有更好的方法,我当然愿意听取建议。
When..Do和Arg.的确切顺序。Do没有定义,我不建议依赖它,因为我认为它可能在不同版本之间根据实现而改变。(如果你有一个很好的理由以一个特定的顺序定义它,请将你的建议发布到用户组。)
如果你只想检查logThing是否收到了预期的LogEntry,你可以在事后使用Arg.Is()检查参数:
logThing.Received().AddEntry(Arg.Is(expectedEntry));
如果你需要更复杂的比较逻辑,你可以做同样的事情,但使用一个方法来检查参数:
logThing.Received().AddEntry(Arg.Is<LogEntry>(x => CheckLogEntry(x)));
CheckLogEntry
可以做任何你需要的检查,并且你可以像以前一样保留When.. do throw。
如果您需要使用When..Do方法,您可以保留现有的方法,但将参数的捕获移动到Do
调用中,以确保您期望的调用顺序:
logThing.When(
lt => lt.AddEntry(Arg.Any<LogEntry>())).Do(
call =>
{
actualEntries.Add(call.Arg<LogEntry>());
throw new InvalidOperationException("testMessage");
});
关于其他测试方法的建议,我不完全清楚您在这里要做什么,但是如果您在隔离包装输出来自哪里时遇到麻烦,也许您可以将此责任转移到另一个依赖项?这将允许您替换这个依赖项,并从测试的角度精确地控制发生这种情况的位置。