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. 解决虚拟机联网问题linux VMware eth0

    虚拟机坏了n多回,真是让我装机装到吐血,经过两天的折腾终于弄明白怎么配置虚拟机的静态ip了. 方法: 1. 将虚拟机关机,--> 打开编辑 ->点击 虚拟网络映射 ->点击 VMne ...

  2. MySQL timestamp 的两个属性

    timestamp有两个属性,分别是CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP两种,使用情况分别如下: 1. CURRENT_TIMESTAMP 当要 ...

  3. 网站配置Gittalk教程和解决gittalk的Error: Not Found.问题

    想把网站增加gittalk的评论功能,按照其他教程配置后,出现了 Error: Not Found. 的错误.截图如下: 网上找了很多解决方案,现在贴出来完整的配置Gittalk的教程. 01.新建评 ...

  4. (菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解

    关于网络流: 1.定义 个人理解网络流的意思便是由一条条水管以及一个源点S一个汇点T和一些节点组成的一张图,现在要从S点流水到T点,问怎么流才能让流到T的流量最大.边权表示的是这条水管的最大流量,假设 ...

  5. Spark性能调优篇六之调节数据本地化等待时长

    数据本地化等待时长调节的优化 在项目该如何使用? 通过 spark.locality.wait 参数进行设置,默认为3s,6s,10s. 项目中代码展示: new SparkConf().set(&q ...

  6. [UWP] - 用Json格式来发送一个Post请求到WCF服务

    测试实体类:(需要在客户端和服务端建了有相同字段名称的实体) public class CompositeType { public CompositeType() { SubCompositeTyp ...

  7. 还在使用Future轮询获取结果吗?CompletionService快来了解下吧。

    背景 二胖上次写完参数校验(<二胖写参数校验的坎坷之路>)之后,领导一直不给他安排其他开发任务,就一直让他看看代码熟悉业务.二胖每天上班除了偶尔跟坐在隔壁的前端小姐姐聊聊天,就是看看这些 ...

  8. 开源编解码项目FFmpeg迎来20周年生日 凭一己之力养活全球无数播放器!

    近日,开源编解码库项目FFmpeg迎来20周年生日. 2000.12.20-2020.12.20 可能很多人对于FFmpeg不是特别了解,那么以下几个名字是否大家或多或少都用过呢? 暴风影音.PotP ...

  9. [leetcode]24. Swap Nodes in Pairs交换链表的节点

    感觉这个题后台的运行程序有问题,一开始自己想的是反转链表那道题的方法,只是隔一个节点执行一次,但是没有通过,TLE了,但是很奇怪,并没有死循环,就是最后返回的时候超时. 最后的思路就是很简单的进行交换 ...

  10. Idea创建Maven项目时,没有自动添加Artifacts

    可能的原因是没有进行更新,因为第一次创建时由于要下载东西,所以pom文件是自动改变的,如果没有设置自动更新maven项目,就可能出现这种情况 这时候只要去maven project中点击一下更新按钮, ...