C# 在when..Do for void方法中未按预期触发Arg.Do()

C# 在when..Do for void方法中未按预期触发Arg.Do(),c#,nsubstitute,C#,Nsubstitute,在我的一个测试中,我有下面的结构,旨在测试某个日志是否被正确的复杂参数对象调用,即使它抛出一个异常,该异常随后被包装并通常被进一步操作。 logThing有一个方法: void AddEntry(LogEntry); 所以我用When..Do使它抛出一个异常 public void UnitTest() { // Arrange ILogThing logThing = Substitute.For<ILogThing>() SystemUnderTest

在我的一个测试中,我有下面的结构,旨在测试某个日志是否被正确的复杂参数对象调用,即使它抛出一个异常,该异常随后被包装并通常被进一步操作。 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);
    }
}
public void UnitTest()
{
//安排
ILogThing logThing=将.For()替换为
SystemUnderTest系统=新系统未测试(logThing);
List actualEntries=新列表();
LogEntry expectedEntry=GetSomeTestData();
什么时候(
lt=>lt.AddEntry(Arg.Do(r=>actualEntries.Add(r))).Do(
call=>{抛出新的InvalidOperationException(“testMessage”);});
//表演
尝试
{
system.DoSomethingWhichLogs(someArgs)
}
捕获(WrappedEx)
{
//断言
Assert.AreEqual(1,actualEntries.Count);
Assert.AreEqual(ActualEntry[0],expectedEntry);
}
}
但是,预期的Arg.Do()调用在此设置中从未发生

我在catch块中放置了一个断点,并使用Visual Studio的即时窗口调用logThing上的ReceivedCalls(),它确实有一个使用正确参数调用logThing的记录-只是Arg.Do似乎只有在When..Do块完成后才执行。很明显,这意味着,既然我投进了When…,Do,它就永远不会到达它

我真的没想到NSubstitute会以这种方式订购电话,这是预期的行为吗? 如果是这样的话,我可以做些什么来测试传入的参数,或者我应该把我的参数检查放在主When..do块中(这会使它更难阅读)


测试中的系统会对异常执行各种操作,包括将其与日志条目包装在一起,因此,将所有这些检查放在一个测试中对我来说很有用。我确实考虑过将其拆分为两个单独的测试,但意识到如果我这样做,我无法轻松确定错误的包装输出来自何处(可能是最初生成日志条目的部分,也可能是包装它的部分),当使用此模式时,我可以检查以确保logThing接收到我期望的内容。不过,如果有更好的方法,我当然愿意接受建议。

没有定义..do和Arg.do的确切顺序,我不建议依赖它,因为我认为它可能会根据实现在不同版本之间发生变化。(如果您有充分的理由按照特定顺序定义,请将您的建议发布到。)

如果您只想检查logThing是否收到预期的LogEntry,则可以使用Arg.Is()检查事实之后的参数:

如果需要更复杂的比较逻辑,可以执行相同的操作,但要使用方法检查参数:

logThing.Received().AddEntry(Arg.Is<LogEntry>(x => CheckLogEntry(x)));

关于其他测试方法的建议,我不完全清楚您在这里尝试做什么,但是如果您在隔离包装输出的来源方面遇到困难,那么您可以将此责任转移到另一个依赖项?这将允许您替换此依赖项,并从测试的角度准确地控制发生这种情况的位置。

感谢您对排序(或缺少)的澄清。我没有任何确切的理由希望这样做,只是我希望它发生在参数用于的函数“之前”。最初,我有logThing.Received().AddEntry(Arg.Do(x=>CheckLogEntry(x));但这并没有触发。实际上我没有想过使用.Is,但是我会在检查后返回bool,我认为这是最简洁的方法。是的,Arg.do不会在Received()中触发,因为Received()是在事实之后检查调用。Arg.Do仅为将来接收到的调用激发。您知道是否有方法防止.is()吞噬异常吗?这使得从错误消息中读取断言基本上是不可能的,因为当前从Is中抛出的错误只是表示复杂对象不匹配,它没有指定位置或方式。不,您无法停止正在吞咽异常。我不会根据您传递给Is的方法断言,只会根据它是否匹配而返回true或false。我们目前正致力于实现自定义匹配器,以提供更多信息,但这项工作还没有达到最新版本。我现在已经退回到捕获参数的阶段-我需要知道问题在对象中的何处。我会确保继续检查以后的版本。
logThing.Received().AddEntry(Arg.Is<LogEntry>(x => CheckLogEntry(x)));
logThing.When(
    lt => lt.AddEntry(Arg.Any<LogEntry>())).Do(
    call =>
    {
        actualEntries.Add(call.Arg<LogEntry>());
        throw new InvalidOperationException("testMessage");
    });