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 在 PubSubEvent 的 Subscribe
函数中抛出:
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 的话可以参考 PrismApplicationBase 和 PrismInitializationExtensions 写一个初始化类,大概差不多这样(简化了部分代码):
[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 中可以注册各种方便单元测试的伪对象。
原文转载:http://www.shaoqun.com/a/512311.html
feedback:https://www.ikjzd.com/w/159
beien:https://www.ikjzd.com/w/1336
1.问题[TestClass]publicclassUnitTest1{[TestMethod]publicvoidTestMethod1(){ContainerLocator.Container.Resolve<TestViewModel>();}}publicclassTestViewModel{publicTestViewModel(IEventAggregatoreventAg
亚马逊全球开店制造:亚马逊全球开店制造
adore:adore
亚马逊卖家必看:亚马逊广告建议竞价怎么用最合理?:亚马逊卖家必看:亚马逊广告建议竞价怎么用最合理?
云南腾冲的最佳旅游季节是什么时候?:云南腾冲的最佳旅游季节是什么时候?
占全国半壁江山,深圳跨境电商"东西齐飞":占全国半壁江山,深圳跨境电商"东西齐飞"
No comments:
Post a Comment