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

如何用NSubstitute编写模拟单元测试,测试含向下转型与受保护内部方法的类方法

问题:Mock Protected Internal 方法以测试向下转型的工厂方法

被测类CarWrapperFactory的核心逻辑是将接口参数向下转为具体类,调用其受保护的内部方法后生成实例,代码如下:

public class CarWrapperFactory : ICarWrapperFactory
{
    public ICarWrapper CreateCarWrapper(ICarConfigurationWrapper carConfigurationWrapper)
    {
        // 向下转型并调用protected internal方法
        CarConfiguration loggerConfiguration = ((CarConfigurationWrapper)carConfigurationWrapper).GetCarConfigurationAdaptee();

        return new CarWrapper(carConfiguration.CreateCar());
    }
}

当前基于NSubstitute编写的测试代码框架如下,需要补充MockGetCarConfigurationAdaptee方法的逻辑:

ICarConfigurationWrapper carConfigurationWrapperMock = Substitute.For<ICarConfigurationWrapper>();

// 需要补充代码:Mock protected internal方法GetCarConfigurationAdaptee

ICarWrapperFactory carWrapperFactorySUT = new CarWrapperFactory();

ICarWrapper carWrapper = carWrapperFactorySUT.CreateCarWrapper(carConfigurationWrapperMock);

补充背景:实际场景为适配第三方开源日志框架,该框架核心配置类未提供工厂模式,需通过实例方法创建日志实例。


解决方案

要Mockprotected internal方法,需结合.NET访问规则与NSubstitute的特性处理,具体步骤如下:

1. 开放程序集内部成员访问权限

protected internal方法默认仅允许同一程序集或授权程序集访问,需在CarConfigurationWrapper所在程序集的配置中,添加测试程序集的访问权限:

  • 若使用.NET Framework,在AssemblyInfo.cs中添加:
    [assembly: InternalsVisibleTo("你的测试程序集名称")]
    
  • 若使用.NET Core/.NET 5+,在项目文件(.csproj)中添加:
    <ItemGroup>
      <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
        <_Parameter1>你的测试程序集名称</_Parameter1>
      </AssemblyAttribute>
    </ItemGroup>
    

2. 创建可测试的子类(针对可修改的类)

如果CarConfigurationWrapper是自主维护的类,可创建测试子类重写目标方法,暴露预设的返回值:

public class TestableCarConfigurationWrapper : CarConfigurationWrapper
{
    private readonly CarConfiguration _mockAdaptee;

    public TestableCarConfigurationWrapper(CarConfiguration mockAdaptee)
    {
        _mockAdaptee = mockAdaptee;
    }

    protected internal override CarConfiguration GetCarConfigurationAdaptee()
    {
        return _mockAdaptee;
    }
}

注意:原方法需标记为virtual才能被重写,若未标记,需先修改原类代码。

3. 替换测试对象并执行验证

不再MockICarConfigurationWrapper接口,直接使用测试子类实例,传入预设好的CarConfiguration Mock对象:

// Mock CarConfiguration及其CreateCar方法
var mockCarConfig = Substitute.For<CarConfiguration>();
var mockCar = Substitute.For<ICar>();
mockCarConfig.CreateCar().Returns(mockCar);

// 创建测试用的配置包装类实例
ICarConfigurationWrapper configWrapper = new TestableCarConfigurationWrapper(mockCarConfig);

// 执行测试
var sut = new CarWrapperFactory();
var result = sut.CreateCarWrapper(configWrapper);

// 验证结果
Assert.NotNull(result);
mockCarConfig.Received(1).CreateCar();

4. 第三方类的特殊处理(如日志框架场景)

如果CarConfigurationWrapper是第三方框架类(无法修改源码),可采用以下方式:

  • 直接实例化真实的配置包装类,配置其内部的核心配置对象为测试专用实例;
  • 若必须Mock,可通过反射获取GetCarConfigurationAdaptee方法的引用,手动调用或预设返回值(此方式复杂度较高,优先推荐前者)。

内容的提问来源于stack exchange,提问作者crazyTech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:04:56