如何使用NSubstitute模拟IConfiguration的Get扩展方法
问题场景
我在开发ASP.NET Core 6 Web API项目,项目appsettings.json配置片段如下:
"MainSection": { "FirstSubsection":{ "SomeKey": "someValue" }, "SecondSubSection": { SomeKey: "somevalue" } }
业务代码中通过强类型类Foo读取配置,调用代码如下:
configuration .GetSection("MainSection") .GetSection("SecondSection") .Get<Foo>();
其中Get<T>()是Microsoft.Extensions.Configuration下提供的配置绑定扩展方法。
遇到的问题
尝试用NSubstitute模拟Get<Foo>()方法时,首先写出如下代码,抛出错误Can not return value of type Foo for IDisposable.Dispose (expected type void):
var section = Substitute.For<IConfigurationSection>(); section.Get<Foo>().Returns(new Foo(){ SomeProperty = "aa"});
已知NSubstitute无法直接模拟静态扩展方法,之后又尝试直接给配置节点设置Key和Value:
section.Key.Returns("SomeKey"); section.Value.Returns("someValue");
但调用Get<Foo>()时仍然返回null,需要正确的模拟实现方案。
解决方案
核心原因:Get<T>()是静态扩展方法,NSubstitute无法直接拦截模拟,该方法的底层逻辑是读取目标IConfigurationSection的所有子节点,按属性名匹配绑定到强类型对象。单独设置节点本身的Key和Value无效,因为绑定逻辑读取的是当前节点下的子级节点,而非节点自身的值。
两种可行实现方案:
- 方案一(推荐):使用内存配置构建真实配置对象
不需要模拟任何接口,直接用微软提供的内存配置提供器构造和生产环境行为完全一致的IConfiguration实例,稳定性最高,不需要维护模拟逻辑:var inMemoryConfig = new Dictionary<string, string> { // 配置层级用冒号分隔,最后一级和Foo类的属性名对应 {"MainSection:SecondSubSection:SomeProperty", "aa"} }; IConfiguration configuration = new ConfigurationBuilder() .AddInMemoryCollection(inMemoryConfig) .Build(); // 直接调用原有读取逻辑即可拿到正确绑定的Foo实例 var foo = configuration .GetSection("MainSection") .GetSection("SecondSubSection") .Get<Foo>(); - 方案二:使用NSubstitute完整模拟配置节点结构
如果必须用NSubstitute(比如需要验证配置读取的调用路径、次数等场景),需要模拟出配置节点的子节点返回逻辑,让Get<T>()扩展方法能正常读取到待绑定的属性值:// 1. 构造Foo属性对应的子节点,Key和属性名一致,Value为属性值 var propSection = Substitute.For<IConfigurationSection>(); propSection.Key.Returns("SomeProperty"); propSection.Value.Returns("aa"); // 2. 构造SecondSubSection节点,返回上面的属性节点作为子节点 var secondSection = Substitute.For<IConfigurationSection>(); secondSection.GetChildren().Returns(new[] { propSection }); // 3. 构造MainSection节点,配置GetSection返回SecondSubSection var mainSection = Substitute.For<IConfigurationSection>(); mainSection.GetSection("SecondSubSection").Returns(secondSection); // 4. 构造根配置对象,配置GetSection返回MainSection var mockConfig = Substitute.For<IConfiguration>(); mockConfig.GetSection("MainSection").Returns(mainSection); // 此时调用原有读取逻辑即可正常拿到绑定后的Foo实例 var foo = mockConfig .GetSection("MainSection") .GetSection("SecondSubSection") .Get<Foo>();
注意:配置绑定默认是大小写不敏感的,节点Key和属性名大小写不一致通常也能正常绑定,但建议保持命名一致避免异常。如果Foo包含嵌套复杂属性,只需要按层级继续构造对应子节点的
GetChildren()返回值即可。
内容的提问来源于stack exchange,提问作者MiBuena
相关产品推荐
相关产品推荐

