1. 问题

[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
ContainerLocator.Container.Resolve<TestViewModel>();
}
} public class TestViewModel
{
public TestViewModel(IEventAggregator eventAggregator)
{
var testEvent = eventAggregator.GetEvent<TestEvent>();
testEvent.Subscribe(() => { }, ThreadOption.UIThread);
}
} public class TestEvent : PubSubEvent
{ }

上面是一段使用了 Prism 的单元测试,它主要的逻辑是在 EventAggregator 中订阅了 TestEvent,当接收到消息后在 UI 线程上执行后续的逻辑。这种代码在正常程序中没有问题,但在单元测试中会报错:

System.InvalidOperationException: To use the UIThread option for subscribing, the EventAggregator must be constructed on the UI thread.

2. 原因

翻翻源码,可以发现这个 Exception 在 PubSubEventSubscribe 函数中抛出:

switch (threadOption)
{
case ThreadOption.PublisherThread:
subscription = new EventSubscription(actionReference);
break;
case ThreadOption.BackgroundThread:
subscription = new BackgroundEventSubscription(actionReference);
break;
case ThreadOption.UIThread:
if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
subscription = new DispatcherEventSubscription(actionReference, SynchronizationContext);
break;
default:
subscription = new EventSubscription(actionReference);
break;

SynchronizationContext 为 null 时就会判断当前不在 UI 线程,然后抛出 Exception。而 SynchronizationContext 又是在 EventAggregator 中赋值:

private readonly SynchronizationContext syncContext = SynchronizationContext.Current;

public TEventType GetEvent<TEventType>() where TEventType : EventBase, new()
{
lock (events)
{
EventBase existingEvent = null; if (!events.TryGetValue(typeof(TEventType), out existingEvent))
{
TEventType newEvent = new TEventType();
newEvent.SynchronizationContext = syncContext;
events[typeof(TEventType)] = newEvent; return newEvent;
}
else
{
return (TEventType)existingEvent;
}
}
}

问题就出在 SynchronizationContext.Current 这里。这个属性用于获取当前线程的同步上下文。不是每一个线程都有一个 SynchronizationContext 对象。一个总是有 SynchronizationContext 对象的是UI线程。由于单元测试并不是运行在 UI 线程,所以这个属性在单元测试中一直为 null。

3. 解决方案

现在我们知道问题原因了,解决方案也很简单,只要自定义一个 EventAggregator,源码全部照抄,但是把这句:

private readonly SynchronizationContext syncContext = SynchronizationContext.Current;

替换成这句:

private readonly SynchronizationContext syncContext = new SynchronizationContext();

就不会出现 PubSubEvent 中 SynchronizationContext 等于 null 的情况了。然后再把这个类注册到容器中作为 IEventAggregator:

ContainerLocator.Current.RegisterSingleton<IEventAggregator, MyEventAggregator>();

4. 最后

根据单元测试项目的结构,容器的初始化会有不同的方式,如果想尽量模仿 PrismApplication 的话可以参考 PrismApplicationBasePrismInitializationExtensions 写一个初始化类,大概差不多这样(简化了部分代码):

[TestClass]
public abstract class TestInitializerBase
{
public void Initialize()
{
ContainerLocator.SetContainerExtension(() => new UnityContainerExtension());
ContainerExtension = ContainerLocator.Current; ContainerExtension.RegisterSingleton<IDialogService, DialogService>();
ContainerExtension.RegisterSingleton<IModuleInitializer, ModuleInitializer>();
ContainerExtension.RegisterSingleton<IModuleManager, ModuleManager>();
ContainerExtension.RegisterSingleton<RegionAdapterMappings>();
ContainerExtension.RegisterSingleton<IRegionManager, RegionManager>();
ContainerExtension.RegisterSingleton<IRegionNavigationContentLoader, RegionNavigationContentLoader>(); ContainerExtension.RegisterSingleton<IEventAggregator, EventAggregator>(); ContainerExtension.RegisterSingleton<IRegionViewRegistry, RegionViewRegistry>();
ContainerExtension.RegisterSingleton<IRegionBehaviorFactory, RegionBehaviorFactory>();
ContainerExtension.Register<IRegionNavigationJournalEntry, RegionNavigationJournalEntry>();
ContainerExtension.Register<IRegionNavigationJournal, RegionNavigationJournal>();
ContainerExtension.Register<IRegionNavigationService, RegionNavigationService>(); RegisterRequiredTypes(ContainerExtension); } public IContainerExtension ContainerExtension { get; private set; } protected abstract void RegisterRequiredTypes(IContainerRegistry containerRegistry);
} public class TestInitializer : TestInitializerBase
{
[AssemblyInitialize]
public static void InitializeAseemble(TestContext testContext)
{
var testInitializer = new TestInitializer();
testInitializer.Initialize();
} protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IEventAggregator, MyEventAggregator>();
}
}

这样在 TestInitializer 中可以注册各种方便单元测试的伪对象。

[WPF] 在单元测试中使用 Prism 的 EventAggregator,订阅到 ThreadOption.UIThread 会报错的更多相关文章

  1. 【docker】【redis】2.docker上设置redis集群---Redis Cluster部署【集群服务】【解决在docker中redis启动后,状态为Restarting,日志报错:Configured to not listen anywhere, exiting.问题】【Waiting for the cluster to join...问题】

    参考地址:https://www.cnblogs.com/zhoujinyi/p/6477133.html https://www.cnblogs.com/cxbhakim/p/9151720.htm ...

  2. webRTC中回声消除(AEC)模块编译时aec_rdft.c文件报错:

    webRTC中回声消除(AEC)模块编译时aec_rdft.c文件报错. 原因是: 局部变量ip跟全局变量冲突的问题,可以将局部变量重新命名一下,就可以通过编译了. aec_rdft.c修改以后文件代 ...

  3. VS项目中使用Nuget还原包后编译生产还一直报错?

    Nuget官网下载Nuget项目包的命令地址:https://www.nuget.org/packages 今天就遇到一个比较奇葩的问题,折腾了很久终于搞定了: 问题是这样的:我的解决方案原本是好好的 ...

  4. 在android 中开发java.net.SocketException: socket failed: EACCES (Permission denied) 报错

    在android中下载文件,写好下载文件的代码后需要配置相应的权限 <uses-permission android:name="android.permission.INTERNET ...

  5. MySQL中查询时"Lost connection to MySQL server during query"报错的解决方案

    一.问题描述: mysql数据库查询时,遇到下面的报错信息: 二.原因分析: dw_user 表数据量比较大,直接查询速度慢,容易"卡死",导致数据库自动连接超时.... 三.解决 ...

  6. hive中创建子表并插入数据过程初始化MR报错解决方法

    本文继成上一篇通过hive分析nginx日志文章,详情参考下面链接: http://www.cnblogs.com/wcwen1990/p/7066230.html 接着来: 创建业务子表: drop ...

  7. webstorm中sass编译时目录或内容包含中文字符报错

    ruby版本:ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32] sass版本:Sass 3.4.22 (Selective Steve) ...

  8. react-native 中使用redux 优化 Connect 使用装饰器简化代码报错

    报错信息 error: bundling failed: Error: The 'decorators' plugin requires a 'decoratorsBeforeExport' opti ...

  9. gazebo仿真踩坑--rviz中设定机器人的目标位置,move_base后台日志报错

    启动仿真环境及各种节点(amcl,move_base,map_server)后,在rviz中设定机器人的目标位置,后台日志报错 [ INFO] [1571974242.864525935, 40.51 ...

随机推荐

  1. Java中instanceof注意的地方

    instanceof只能用于对象的判断,不能用于基本类型的判断,以下代码会编译不通过 'A' instanceof Character instanceof特有的规则:若左操作数是null,结果就直接 ...

  2. Numpy的学习5-array的分割

    import numpy as np A = np.arange(12).reshape((3, 4)) print(A) """ array([[ 0, 1, 2, 3 ...

  3. Maven史上最全的pom.xml详解

    下面主要是借鉴 官网的资料 收集而来 主要是为了讲解,用到的很少,但是还是需要了解 ,重点是方便查验资料 <project xmlns="http://maven.apache.org ...

  4. MySQL_CRUD_In_Terminal

    MySQL的CRUD操作 从Terminal中,可以对数据库进行链接,无需GUI界面就可以对数据库进行相关操作.对于Linux.Windows.MacOS,也可以使用可视化软件Navicat.MySQ ...

  5. 持续提升程序员幸福指数——使用abp vnext设计一款面向微服务的单体架构

    可能你会面临这样一种情况,在架构设计之前,你对业务不甚了解,需求给到的也模棱两可,这个时候你既无法明确到底是要使用单体架构还是使用微服务架构,如果使用单体,后续业务扩展可能带来大量修改,如果使用微服务 ...

  6. [leetcode]TwoSum系列问题

    1.普通数组找两个数,哈希表建立数值和下标的映射,遍历时一边判断一边添加 /* 哇,LeetCode的第一题...啧啧 */ public int [] twoSum(int[] nums, int ...

  7. device_create为何可以在/sys/class/具体类/属性文件?怎么实现的

    答案: 版本3.6.9: device_create -> device_register -> device_add -> device_add_attrs -> devic ...

  8. Selenium执行JavaScript脚本

    JavaScript是运行在客户端(浏览器)和服务器端的脚本语言,允许将静态网页转换为交互式网页.可以通过 Python Selenium WebDriver 执行 JavaScript 语句,在We ...

  9. 用Margin还是用Padding的区别

    用margin还是用padding这个问题是每个学习CSS进阶时的必经之路. CSS边距属性定义元素周围的空间.通过使用单独的属性,可以对上.右.下.左的外边距进行设置.也可以使用简写的外边距属性同时 ...

  10. 解析SwiftUI布局细节(二)循环轮播+复杂布局

    前言 上一篇我们总结的主要是VStack里面的东西,由他延伸到 @ViewBuilder, 接着我们上一篇总结的我们这篇内容主要说的是下面的几点,在这些东西说完后我准备解析一下苹果在SiwftUI文档 ...