如何在NSubstitute中模拟内部成员并通过单元测试?
解决NSubstitute模拟内部辅助类的问题
你的问题主要出在两个地方:一是直接替代A类后试图访问其只读内部字段_bHelper,二是错误地在非替代对象的方法调用中使用了Arg.Any匹配器。下面给你两种可行的解决方案:
方案一:通过构造函数注入依赖(推荐)
这种方式更符合依赖注入原则,也让测试逻辑更清晰。首先修改A类,添加一个接受B实例的构造函数,这样测试时可以传入模拟的B对象:
[assembly: InternalsVisibleTo("Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] namespace NS.B { public class A { internal readonly B _bHelper; // 供测试使用的构造函数,传入B的实例 public A(B bHelper) { _bHelper = bHelper; } // 保留默认构造函数,用于正常业务逻辑 public A() : this(new B()) {} public int GetBag(string s1, string s2){ return _bHelper.GetBag(s1, s2); } } }
然后修改测试代码,先模拟B类,再传入A的实例:
[Fact] public void checkBaggageRule() { // 模拟内部类B var mockB = Substitute.For<B>(); // 设置当调用GetBag时,无论传入什么参数都返回30 mockB.GetBag(Arg.Any<string>(), Arg.Any<string>()).Returns(30); // 创建A的实例,传入模拟的B var aInstance = new A(mockB); var result = aInstance.GetBag("oo", "L"); Assert.Equal(30, result); }
这样做的好处是不需要依赖反射,代码更健壮,也符合良好的设计原则。
方案二:使用反射修改只读字段(备选,适用于无法修改原类的情况)
如果因为遗留代码等原因无法修改A类的构造函数,可以通过反射来替换_bHelper字段的值:
[Fact] public void checkBaggageRule() { var mockB = Substitute.For<B>(); mockB.GetBag(Arg.Any<string>(), Arg.Any<string>()).Returns(30); // 创建A的默认实例 var aInstance = new A(); // 通过反射获取_bHelper字段 var bHelperField = typeof(A).GetField("_bHelper", BindingFlags.Instance | BindingFlags.NonPublic); // 替换字段值为模拟的B对象 bHelperField.SetValue(aInstance, mockB); var result = aInstance.GetBag("oo", "L"); Assert.Equal(30, result); }
为什么原来的代码会报错?
你之前直接Substitute.For<A>()得到的是A的替代实例,但_bHelper是只读内部字段,NSubstitute不会自动替换这个字段的值,此时_bHelper要么是null,要么是B的实际实例。当你调用repo._bHelper.GetBag(Arg.Any<string>(), Arg.Any<string>())时,Arg.Any被用在了非替代对象的方法调用中,这违反了NSubstitute的规则——参数匹配器只能用于替代对象的方法调用参数,所以才会抛出UnexpectedArgumentMatcherException。
内容的提问来源于stack exchange,提问作者Navid_pdp11
相关产品推荐
相关产品推荐

