You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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();

}

解答

结论:可以测试构造函数

构造函数里的日志调用、事件订阅都是可测试的逻辑点,完全可以针对性验证。

问题原因分析

  1. NullReferenceException:你Mock了IEventAggregator,但UpdateSomeStateEvent是具体类且Subscribe不是虚方法,Moq无法生成有效的Mock实例,导致GetEvent返回的对象为空,调用Subscribe时触发空引用。
  2. 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非可重写的具体类方法,能直接实例化的就直接用实例。
  • 尽量MockMyContext这类依赖外部资源的对象,避免测试依赖真实数据库或其他外部服务。
  • 构造函数测试的核心是验证初始化逻辑:依赖是否正确注入、必要的初始化动作(日志、订阅)是否执行。

内容的提问来源于stack exchange,提问作者Hans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 11:22:09