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 中可以注册各种方便单元测试的伪对象。

最新文章

  1. Linux驱动开发——pr_fmt的用法
  2. 用vue2 +vue-router2 + es6 +webpack 高仿饿了么app(干货满满)
  3. TabControl控件
  4. Codeforces Round #334 (Div. 1) C. Lieges of Legendre
  5. POI导出数据内存溢出问题
  6. maven install 跳过 测试 test
  7. .Net语言中关于AOP 的实现详解
  8. 【LeetCode】205. Isomorphic Strings
  9. 201521123047 j第五周学习总结
  10. 云计算---OpenStack Neutron详解
  11. 关于 target=&quot;_blank&quot;漏洞的分析
  12. 学习Python第四天
  13. Web设计中打开新页面或页面跳转的方法
  14. LOJ6089 小Y的背包计数问题 背包、根号分治
  15. C/C++宏定义交换两个值
  16. 深入理解java虚拟机---虚拟机工具jinfo(十五)
  17. bzoj3029 守卫者的挑战 (多维dp)
  18. 工作流JBPM_day01:6-执行流程实例
  19. C# 特性 System.ComponentModel 命名空间属性方法大全,System.ComponentModel 命名空间的特性
  20. [iOS微博项目 - 4.6] - 微博配图

热门文章

  1. elementUI的动态tabs页的使用,vue的动态组件的操作
  2. 云原生网络代理(MOSN)的进化之路
  3. 基于Python PIL实现简单图片格式转化器
  4. php代码审计整理
  5. angular8 大地老师学习笔记---第八课
  6. WIN7远程桌面连接提示:“发生身份验证错误。要求的函数不受支持”
  7. ProGuard使用文档
  8. execute,executeQuery,executeUpdate的区别是什么?
  9. 前端JS获取用户位置
  10. C++作用域限定符:private/public与protected