C#单元测试:不可修改原类时构造函数调用报错解决方案
单元测试绕过基类构造函数认证依赖的实现方案
问题背景
以下代码来自主项目引用的NuGet包,主项目通过认证逻辑调用其中的MyClass类,GetMyAppStoreConfig()方法仅在主项目携带认证上下文发起调用时可正常运行。
public class MyClass : MyClassBase { private SomeClass1 someClass1; private MyAppStore myAppStore; // 新增字段 public MyClass(params object[] args) : base(args) { this.someClass1 = new SomeClass1(); this.myAppStore = new MyAppStore(GetMyAppStoreConfig()); // 新增初始化逻辑 } protected override async Task<SomeValues> Process( SomeClass2 someClass2, SomeClass3 someClass3, SomeClass4 someClass4 ) { // 业务逻辑实现 } // 新增方法,依赖myAppStore字段 protected override async Task<SomeValues> ProcessWithAuth( SomeClass5 someClass5 ) { // 带认证逻辑的业务实现 } private AppStoreConfig GetMyAppStoreConfig(){ // 依赖认证上下文的配置获取逻辑 } }
原有针对该类的单元测试代码如下:
[TestClass()] public class MyClassTests : MyClass { [TestMethod()] public void test1() { // 测试逻辑 // 内部调用this.Process(someClass2,someClass3,someClass4) } }
现存问题
当前测试无法正常运行,实例化测试类时会在GetMyAppStoreConfig()方法处抛出异常。本次测试暂不需要覆盖ProcessWithAuth()方法,需要在满足以下约束的前提下调整测试用例,让测试正常运行:
- 无法为
MyClass新增额外构造函数,也不可修改MyClass.cs中任何成员的访问修饰符 - 测试需要运行
Process()方法中的真实业务逻辑,不可直接对该方法进行Mock处理
实现方案
异常触发的核心原因是:测试类实例化时默认调用基类MyClass的构造函数,构造函数中执行的GetMyAppStoreConfig()依赖运行时认证上下文,单元测试环境不满足该条件导致报错。
由于待测试的Process()方法仅依赖私有字段someClass1,完全不涉及新增的myAppStore字段及相关认证逻辑,可以通过跳过构造函数创建实例+反射初始化必要依赖的方式解决,具体步骤如下:
- 调用
System.Runtime.Serialization.FormatterServices.GetUninitializedObject()创建MyClass实例,该方法会直接分配类型对应的内存空间,不会执行任何构造函数逻辑,从根源避免触发GetMyAppStoreConfig()调用 - 通过反射获取
MyClass的私有字段someClass1,为其赋值new SomeClass1()实例,满足Process()方法运行的依赖要求 - 由于
Process()方法不访问myAppStore字段,无需对该字段做额外处理,直接通过反射调用Process()方法即可执行完整的真实业务逻辑
调整后的测试代码示例:
using System.Reflection; using System.Runtime.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass()] public class MyClassTests { [TestMethod()] public async Task test1() { // 跳过构造函数创建MyClass实例 MyClass instance = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); // 反射获取私有字段someClass1并初始化 FieldInfo someClass1Field = typeof(MyClass).GetField("someClass1", BindingFlags.Instance | BindingFlags.NonPublic); someClass1Field.SetValue(instance, new SomeClass1()); // 反射获取受保护的Process方法 MethodInfo processMethod = typeof(MyClass).GetMethod("Process", BindingFlags.Instance | BindingFlags.NonPublic); // 构造测试入参 var someClass2 = new SomeClass2(); var someClass3 = new SomeClass3(); var someClass4 = new SomeClass4(); // 调用真实Process方法执行业务逻辑 SomeValues result = await (Task<SomeValues>)processMethod.Invoke(instance, new object[] { someClass2, someClass3, someClass4 }); // 后续断言逻辑 } }
注意:如果
MyClassBase基类的Process方法依赖基类中初始化的其他字段,可通过同样的反射方式对必要字段进行赋值,仅需跳过myAppStore相关的初始化逻辑即可。
内容的提问来源于stack exchange,提问作者Saif Haider
相关产品推荐
相关产品推荐

