You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:48:24