如何Mock类成员或强制构造函数返回Mock对象?C# Moq框架测试场景
实现方案
核心问题说明
Thread 是C#中的密封类,没有公开的虚成员,Moq等常规动态代理Mock框架无法直接对它进行Mock,所以我们优先通过依赖注入改造的方式解决测试需求,这也是最符合单元测试规范的实现方式。
方案1:依赖注入改造(推荐,Moq/NSubstitute均可支持)
步骤1:抽象Thread行为为接口
public interface IThread { bool IsAlive { get; } void Interrupt(); // 业务中用到的其他Thread成员可以按需补充 }
步骤2:实现Thread适配器
public class ThreadAdapter : IThread { private readonly Thread _innerThread; public ThreadAdapter(ThreadStart startProc) { _innerThread = new Thread(startProc); } public bool IsAlive => _innerThread.IsAlive; public void Interrupt() => _innerThread.Interrupt(); }
步骤3:改造原有Foo类
改造后完全兼容原有业务逻辑,不需要修改上层调用代码:
using System.Threading; public class Foo { private readonly IThread _thread; // 供测试用的注入构造函数 public Foo(IThread thread) { _thread = thread; } // 保留原有默认构造函数,不影响现有业务 public Foo() : this(new ThreadAdapter(ThreadProc)){} public static void ThreadProc() { for (int i = 0; i < 10; i++) { Thread.Sleep(0); } } public virtual void Interrupt() { if (_thread.IsAlive) { _thread.Interrupt(); } } }
步骤4:编写Moq测试用例
测试场景1:线程存活时调用Interrupt会触发Thread的Interrupt方法
[Test] public void Interrupt_ThreadIsAlive_TriggerInterruptCall() { var mockThread = new Mock<IThread>(); mockThread.Setup(t => t.IsAlive).Returns(true); var foo = new Foo(mockThread.Object); foo.Interrupt(); // 验证Interrupt方法被调用1次 mockThread.Verify(t => t.Interrupt(), Times.Once); }
测试场景2:线程未存活时调用Interrupt不会触发Thread的Interrupt方法
[Test] public void Interrupt_ThreadIsNotAlive_NoInterruptCall() { var mockThread = new Mock<IThread>(); mockThread.Setup(t => t.IsAlive).Returns(false); var foo = new Foo(mockThread.Object); foo.Interrupt(); // 验证Interrupt方法从未被调用 mockThread.Verify(t => t.Interrupt(), Times.Never); }
方案2:不改原有代码的替代方案
如果不允许修改原有业务类代码,可以用以下两种方式实现:
- 付费隔离框架:使用Typemock、JustMock等支持密封类非虚成员Mock的框架,可以直接Mock私有Thread字段
- 反射替换:通过反射把Foo实例的私有
thread字段替换为自己构造的Thread实例,通过状态判断验证逻辑,缺点是无法直接验证方法调用次数,仅能验证行为结果。
其他可选框架推荐
如果想要更简洁的语法,可以用NSubstitute替代Moq,思路和上述方案完全一致,Mock代码会更简洁易读。
内容的提问来源于stack exchange,提问作者Milan
相关产品推荐
相关产品推荐

