为什么在 NSubstitute 模拟上为参数匹配器注册另一个 return 值并使用不同的值会抛出 NullReferenceException

Why does registering another return value on an NSubstitute mock with a different value for an argument matcher throw a NullReferenceException

我有一个针对 .NET 4.6 的 .NET Core Xunit 项目,它使用 NSubstitute 3.1.0。我正在设置一个模拟对象,它需要根据收到的不同参数 return 不同的东西。这是一些示例代码(以及完整项目源代码的 link to the github repo):

public class UnitTest1
{
    private IFoo getMockFoo()
    {
        int instanceIDPool = 1;
        IFoo foo = Substitute.For<IFoo>();
        foo.AddBar(Arg.Is<BarInfoObj>(x => x.ServerName.Contains("SuccessAddNewBar"))).Returns(ci =>
        {
            BarInfoObj barInfoObjToReturn = ci.ArgAt<BarInfoObj>(0).ShallowCopy();
            barInfoObjToReturn.BarInstanceId = instanceIDPool++;
            barInfoObjToReturn.Status = BarStatus.Idle;
            barInfoObjToReturn.StatusMessage = "Newly registered Bar";
            barInfoObjToReturn.StatusReportDateTimeUTC = TimeZoneInfo.ConvertTimeToUtc(DateTime.Now);
            string msg = $"Added Bar at {barInfoObjToReturn.BarUrl} with instance ID {barInfoObjToReturn.BarInstanceId.ToString()} to the Foo's pool of Bars.";
            return Response<BarInfoObj>.Success(barInfoObjToReturn, msg);
        });
        //This second return value registration throws a NullReferenceException.
        //This won't throw an exception if I comment out the return registration above; the
        //exception is thrown whenever there is more than one return value registration.
        foo.AddBar(Arg.Is<BarInfoObj>(x => x.ServerName.Contains("SuccessAddExistingBar"))).Returns(ci =>
        {
            BarInfoObj barInfoObjToReturn = ci.ArgAt<BarInfoObj>(0).ShallowCopy();
            barInfoObjToReturn.BarInstanceId = instanceIDPool++;
            barInfoObjToReturn.Status = BarStatus.Idle;
            barInfoObjToReturn.StatusMessage = "Re-registered Bar";
            barInfoObjToReturn.StatusReportDateTimeUTC = TimeZoneInfo.ConvertTimeToUtc(DateTime.Now);
            string msg = $"Re-registered Bar at {barInfoObjToReturn.BarUrl} with instance ID {barInfoObjToReturn.BarInstanceId.ToString()}.";
            return Response<BarInfoObj>.Success(barInfoObjToReturn, msg);
        });

        return foo;
    }

    [Fact]
    public void Test1()
    {
        //Arrange
        IFoo testFoo = getMockFoo();
        BarInfoObj testInputBar = new BarInfoObj()
        {
            ServerName = "SuccessAddNewBar",
            BarUrl = "http://some.test.url",
            BarInstanceId = 1,
            Status = BarStatus.Idle,
            StatusMessage = "TestInput",
            StatusReportDateTimeUTC = DateTime.Now
        };

        //Act
        Response<BarInfoObj> testResponse = testFoo.AddBar(testInputBar);

        //Assert
        //Just some arbitrary test; the test isn't important because the exception happens
        //when creating the mock.
        testFoo.ReceivedCalls();
    }
}

当我调试单元测试代码时,我注意到 AddBar 方法的第二个 return 值注册抛出 NullReferenceException。我试过注释掉每一个,当他们 运行 自己的时候,他们很好;没有异常被抛出。当 AddBar 方法有两个或更多 return 值注册时,将抛出异常。如果我只是 运行 单元测试,不调试,不会抛出任何异常,测试就会成功。我在另一组没有抛出任何异常的单元测试中做了类似的事情,我无法弄清楚为什么这个有问题。我遵循了 NSubstitute 文档,看来这应该是可行的。我希望另一双眼睛可以帮助我指明正确的方向。

感谢回购。我相信这是因为存根第二个调用的过程最终使用 null 参数调用第一个存根 (Arg.Is returns null) 而发生。 NSubstitute 意识到这个调用不匹配,因此处理 NullReferenceException。测试应该仍然按要求工作——它只是在启用“异常中断”时影响调试。

如果您更新到 NSubstitute 4.x 这种情况应该无一例外地处理,因为有一些代码可以检测正在存根的调用,因此不会检查第一个存根。