如何用Xunit和Moq测试含Prism Event Aggregator订阅的DBConnector?
如何用Xunit和Moq测试DBConnector构造函数?
问题描述
刚接触Xunit和Moq测试,想知道能不能测试DBConnector的构造函数?如果可以该怎么操作?尝试用Mock实现时,先遇到eventAggregator.GetEvent<UpdateSomeStateEvent>().Subscribe((x) => UpdateSomeState(x))引发的NullReferenceException,修复后又出现错误:
Unsupported expression: p => p.Subscribe (It.IsAny
()). Non-overridable members (here: PubSubEvent.Subscribe) may not be used in setup / verification expression.
待测试代码
public DBConnector(MyContext context , ILogger loggerFacade , IEventAggregator eventAggregator) { mContext = context; mLoggerFacade = loggerFacade; mLoggerFacade.Log(LogLevel.Debug , "Some text"); eventAggregator.GetEvent<UpdateSomeStateEvent>().Subscribe((x) => UpdateSomeState(x)); } private void UpdateSomeState(SomeState x) { SomeState = x; }
我的Mock设置代码
public class DBConnectorClassData : IEnumerable<object[]> { public IEnumerator<object[]> GetEnumerator() { var mLogger = new Mock<ILogger>(); ILogger logger = mLogger.Object; var mEA = new Mock<IEventAggregator>(); Mock<UpdateSomeStateEvent> mEvent = new Mock<UpdateSomeStateEvent>(); Action<SomeState> callback = null; mEA.Setup(e => e.GetEvent<UpdateSomeStateEvent>()).Returns(mEvent.Object); IEventAggregator eventAggregator = mEA.Object; var ctxt = new MyContext(); yield return new object[] { new DBConnector(ctxt, logger, eventAggregator) }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
解答
结论:可以测试构造函数
构造函数里的日志调用、事件订阅都是可测试的逻辑点,完全可以针对性验证。
问题原因分析
- NullReferenceException:你Mock了
IEventAggregator,但UpdateSomeStateEvent是具体类且Subscribe不是虚方法,Moq无法生成有效的Mock实例,导致GetEvent返回的对象为空,调用Subscribe时触发空引用。 - Non-overridable members错误:Moq只能Mock接口、抽象类的虚方法/抽象方法,
PubSubEvent.Subscribe是具体类的非虚方法,无法被Moq拦截或Mock,所以直接MockUpdateSomeStateEvent会报错。
具体解决方法
方案1:直接实例化事件,避免Mock非可重写类型
既然UpdateSomeStateEvent(假设是Prism框架的PubSubEvent子类)本身可以直接实例化,那就不用Mock它,直接创建实例返回给IEventAggregator的Mock:
public class DBConnectorClassData : IEnumerable<object[]> { public IEnumerator<object[]> GetEnumerator() { var mLogger = new Mock<ILogger>(); ILogger logger = mLogger.Object; var mEA = new Mock<IEventAggregator>(); // 直接实例化事件,无需Mock var updateEvent = new UpdateSomeStateEvent(); mEA.Setup(e => e.GetEvent<UpdateSomeStateEvent>()).Returns(updateEvent); IEventAggregator eventAggregator = mEA.Object; // 建议Mock MyContext,避免依赖真实数据库连接 var mockContext = new Mock<MyContext>().Object; yield return new object[] { new DBConnector(mockContext, logger, eventAggregator) }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
方案2:验证事件订阅的有效性
如果需要验证构造函数确实完成了事件订阅,并且触发事件时能正确更新状态,可以扩展测试逻辑:
public class DBConnectorTests { [Fact] public void Constructor_InitializesDependenciesAndSubscribesToEvent() { // 1. 准备Mock依赖 var mockLogger = new Mock<ILogger>(); var mockEA = new Mock<IEventAggregator>(); var updateEvent = new UpdateSomeStateEvent(); mockEA.Setup(e => e.GetEvent<UpdateSomeStateEvent>()).Returns(updateEvent); var mockContext = new Mock<MyContext>().Object; // 2. 创建被测试对象 var connector = new DBConnector(mockContext, mockLogger.Object, mockEA.Object); // 3. 验证日志是否被正确调用 mockLogger.Verify(l => l.Log(LogLevel.Debug, "Some text"), Times.Once); // 4. 验证事件订阅是否生效 var testState = new SomeState { /* 填充测试属性 */ }; updateEvent.Publish(testState); Assert.Equal(testState, connector.SomeState); } }
关键注意事项
- 不要Mock非可重写的具体类方法,能直接实例化的就直接用实例。
- 尽量Mock
MyContext这类依赖外部资源的对象,避免测试依赖真实数据库或其他外部服务。 - 构造函数测试的核心是验证初始化逻辑:依赖是否正确注入、必要的初始化动作(日志、订阅)是否执行。
内容的提问来源于stack exchange,提问作者Hans
相关产品推荐
相关产品推荐

