如何在静态类的using语句中模拟(Mock)对象?
如何Mock直接实例化的ClassB(不改动或极小改动被测代码)
这个场景我遇到过好多次——ClassA.MethodA直接在方法内部new了ClassB,没有给依赖注入留口子,确实给单元测试Mock添了不少麻烦。不过咱们有几种办法能搞定,尽量不碰(或者只动一丢丢)被测代码:
方案1:用支持拦截构造函数的商业隔离框架
如果完全不能改动被测代码,这是最直接的方案。像TypeMock Isolator、Telerik JustMock这类框架,能直接拦截new ClassB()的调用,把它替换成你定义好的Mock实例,完全不需要修改业务代码。
举个TypeMock的测试示例:
[Test] public void MethodA_ShouldTriggerDoSomething() { // 创建ClassB的Fake实例 var mockClassB = Isolate.Fake.Instance<ClassB>(); // 拦截所有new ClassB()的调用,返回我们的Fake实例 Isolate.WhenCalled(() => new ClassB()).WillReturn(mockClassB); // 执行被测方法 ClassA.MethodA(); // 验证DoSomething是否被调用 Isolate.Verify.WasCalledWithAnyArguments(() => mockClassB.DoSomething()); }
- 优点:零侵入业务代码,Mock逻辑清晰;
- 缺点:这类框架大多是商业付费的,需要考虑成本。
方案2:给被测代码加个极小的“测试钩子”(优先推荐)
如果能接受对ClassA做几乎可以忽略的改动(只加一个内部委托字段,对原有逻辑完全无影响),那用免费的Mock框架(比如Moq)就能搞定。
先给ClassA加个内部工厂委托:
public static class ClassA { // 内部工厂委托,默认返回new ClassB(),测试时可替换 internal static Func<ClassB> CreateClassB = () => new ClassB(); public static void MethodA() { // 把原来的new ClassB()改成调用工厂委托 using(var classB = CreateClassB()) { classB.DoSomething(); } } }
然后在测试代码里用反射替换这个委托:
[Test] public void MethodA_ShouldCallDoSomething() { // 用Moq创建ClassB的Mock(如果DoSomething是虚方法) var mockB = new Mock<ClassB>(); // 通过反射获取ClassA的内部委托字段并替换 var factoryField = typeof(ClassA).GetField( "CreateClassB", BindingFlags.Static | BindingFlags.NonPublic ); factoryField.SetValue(null, new Func<ClassB>(() => mockB.Object)); // 执行被测方法 ClassA.MethodA(); // 验证DoSomething被调用了一次 mockB.Verify(b => b.DoSomething(), Times.Once); }
- 优点:只需要一行额外的代码,完全不影响原有业务逻辑,用免费框架就能实现;
- 缺点:需要对被测代码做极小改动,但这是非常常见的“测试友好”改造,几乎没有副作用。
方案3:反射直接修改局部变量(不推荐)
你提到的type.GetField思路,这里要注意:classB是MethodA内部的局部变量,不是类的字段,所以直接用GetField根本拿不到它。如果硬要走反射路线,得用调试API或者修改IL代码,复杂度极高,容易出问题,所以非常不推荐。
内容的提问来源于stack exchange,提问作者Dave Matney
相关产品推荐
相关产品推荐

