使用NSubstitute时传递Arg.Any<int>()作为参数引发NullReferenceException问题排查
你碰到的这个坑其实是NSubstitute里很常见的新手误区——Arg.Any
拆解问题原因:
用具体ID时正常运行的逻辑:
当你传入anyId(比如1)调用CalculateWage时,你的替代gateway已经被设置为:只要调用GetWorkingStatistics时传入任意int参数(因为你用了Arg.Any<int>()来匹配),就返回预设的workingStatistics对象。所以_gateway.GetWorkingStatistics(id)能拿到有效的实例,后续访问ws.PayHourly等属性自然不会报错。用Arg.Any
()调用时报错的逻辑 :Arg.Any<int>()本质上是NSubstitute内部的特殊标记,它的作用是告诉NSubstitute:“当这个替代对象的方法被调用时,不管传入什么int参数,都匹配这个规则”——但这个标记只能在设置替代规则的时候用(比如gateway.GetWorkingStatistics(Arg.Any<int>()).Returns(...)这一步)。
当你把它直接传给CalculateWage(Arg.Any<int>())时,这是在实际调用被测方法,此时NSubstitute不会把它当作匹配规则,而是会把它转换成一个不触发预设规则的占位值,导致_gateway.GetWorkingStatistics(id)返回null,后续访问ws.PayHourly就会抛出NullReferenceException。
修正后的测试代码
只需要把调用CalculateWage时的Arg.Any<int>()换成任意有效的int值就行——哪怕是和你设置规则时用的不一样的int也没关系,因为你已经用Arg.Any<int>()让gateway匹配所有int参数了:
[TestFixture] public class CustomerTestsWithNSubstitute { [Test] public void CalculateWage_HourlyPayed_ReturnsCorrectWage() { var gateway = Substitute.For<IDbGateway>(); var workingStatistics = new Business.Demo.WorkingStatistics() { PayHourly = true, HourSalary = 100, WorkingHours = 10 }; // 设置匹配任意int参数的返回规则 gateway.GetWorkingStatistics(Arg.Any<int>()).Returns(workingStatistics); const decimal expectedWage = 100 * 10; var sut = new Customer(gateway); // 这里用任意有效的int值即可,比如1、0、100都可以 decimal actual = sut.CalculateWage(1); Assert.That(actual, Is.EqualTo(expectedWage).Within(0.1)); } }
关键要点总结
Arg.Any<T>()、Arg.Is<T>()这类方法仅用于配置替代对象的调用匹配规则,不能作为实际方法调用的参数。- 实际调用被测方法时,传入符合匹配规则的真实参数值即可——哪怕是随便选的有效值,只要能触发你之前设置的替代规则就行。
内容的提问来源于stack exchange,提问作者Simant

