如何用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
相关产品推荐
相关产品推荐

