C#中如何创建继承Mock的类,无缝复用Setup等Moq功能?
自定义可Mock的SettingsManager实现方案
问题描述
我想创建一个类MockSettingsManager,使其能直接使用Moq框架的Setup等功能,但直接继承Mock不可行。期望实现:初始化时调用CallBase模式下的GetSetting方法,通过Setup设置后返回指定值。以下是我尝试的代码和期望的调用方式:
尝试的类代码
public class MockSettingsManager : Mock // 我知道这样不可行,但想要类似的实现 { private static SettingManagerCore singletonSettingManagerCore = null; private SettingManagerCore SettingManagerCore { get { if (settingManagerCore== null) { Mock<SettingManagerCore> settingManagerCoreMock = new Mock<SettingManagerCore>() { CallBase = true }; settingManagerCore = settingManagerMock.Object; } return singletonSettingManagerCore; } } public virtual T GetSetting<T>(string name) { return SettingManagerCore.GetSetting<T>(name); } }
期望的调用方式
[TestClass] public class MyTests { private MockSettingsManager settingsManagerMock = null!; [TestInitialize] public void Initialize() { settingsManagerMock = new MockSettingsManager() { CallBase = true }; // 此调用应走CallBase var test = settingsManagerMock.Object.GetSetting<string>("Test"); // 此调用应返回Test1 settingsManagerMock.Setup(x => x.GetSetting<string>("Test")).Returns("Test1"); test = settingsManagerMock.Object.GetSetting<string>("Test"); } }
可行解决方案
方案一:基于接口+可Mock子类实现
如果SettingManagerCore未实现接口,建议先抽象出接口,这是最符合Moq设计思想的方案:
- 定义接口(已有则跳过)
public interface ISettingsManager { T GetSetting<T>(string name); }
- 创建可Mock的子类
确保GetSetting是虚方法,或者重写基类的虚方法:
public class MockableSettingsManager : SettingManagerCore, ISettingsManager { public override T GetSetting<T>(string name) { return base.GetSetting<T>(name); } }
- 测试中直接使用Moq
[TestClass] public class MyTests { private Mock<MockableSettingsManager> _settingsManagerMock = null!; [TestInitialize] public void Initialize() { // 开启CallBase,默认走基类实现 _settingsManagerMock = new Mock<MockableSettingsManager>() { CallBase = true }; // 第一次调用走基类的GetSetting逻辑 var test = _settingsManagerMock.Object.GetSetting<string>("Test"); // Setup后返回指定值 _settingsManagerMock.Setup(x => x.GetSetting<string>("Test")).Returns("Test1"); test = _settingsManagerMock.Object.GetSetting<string>("Test"); // 此时返回"Test1" } }
方案二:封装Mock实例的自定义类
如果不想依赖接口,可以封装一个包含Mock<SettingManagerCore>实例的类,对外暴露Moq的核心方法:
public class MockSettingsManager { private readonly Mock<SettingManagerCore> _innerMock; public MockSettingsManager(bool callBase = true) { _innerMock = new Mock<SettingManagerCore>() { CallBase = callBase }; } // 暴露目标对象,用于调用业务方法 public SettingManagerCore Object => _innerMock.Object; // 封装Setup方法,对齐Moq的调用方式 public ISetup<SettingManagerCore, T> Setup<T>(Expression<Func<SettingManagerCore, T>> expression) { return _innerMock.Setup(expression); } // 按需封装其他Moq方法,比如Verify public void Verify<T>(Expression<Func<SettingManagerCore, T>> expression, Times times) { _innerMock.Verify(expression, times); } }
测试调用方式和你期望的几乎一致:
[TestClass] public class MyTests { private MockSettingsManager _settingsManagerMock = null!; [TestInitialize] public void Initialize() { _settingsManagerMock = new MockSettingsManager(callBase: true); // 走CallBase模式下的基类逻辑 var test = _settingsManagerMock.Object.GetSetting<string>("Test"); // Setup后返回指定值 _settingsManagerMock.Setup(x => x.GetSetting<string>("Test")).Returns("Test1"); test = _settingsManagerMock.Object.GetSetting<string>("Test"); // 返回"Test1" } }
关键说明
- Moq的
Mock<T>是泛型类,无法直接非泛型继承,所以初始思路不可行,必须通过封装或基于含虚方法的类/接口实现。 - Moq能拦截方法调用的前提是:目标方法为虚方法,或者类实现了对应的接口,否则无法生成代理进行拦截。
内容的提问来源于stack exchange,提问作者Resident Zero9999
相关产品推荐
相关产品推荐

