【问题标题】:Why isn't my EventAggregator Subscription Handling This Event?为什么我的 EventAggregator 订阅未处理此事件?
【发布时间】:2016-04-24 02:46:19
【问题描述】:

我有一个 Autofac DI 容器,定义如下:

public class Bootstrapper
{
    public IContainer BootStrap()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ItemViewModel>().AsSelf();
        builder.RegisterType<EventAggregator>()
            .As<IEventAggregator>()
            .SingleInstance(); 
     }
}

我定义了一个单元测试来测试删除是否会从集合中删除已删除的项目:

[Fact]
public void Should_remove_item_from_collection_when_item_is_deleted()
{
  const int deletedId = 42;
  // adds three items to the collection
  _openItemEditViewEvent.Publish(deletedId);
  _openItemEditViewEvent.Publish(8);
  _openItemEditViewEvent.Publish(9);

// I've tried this:
     _eventAggregatorMock.Object.GetEvent<ItemDeletedEvent>().Publish(42);
// and alternatively, this (not at the same time):
     _itemDeletedEventMock.Object.Publish(42);  

  Assert.Equal(2,_vm.ItemEditViewModels.Count); // always fails
  Assert.False(_vm.ItemEditViewModels
     .Select(vm => vm.Item.Id).Contains(42), "Wrong item deleted");
}

单元测试的构造函数初始化并将EventAggregator分配给视图模型:

_eventAggregatorMock = new Mock<IEventAggregator>();

_itemDeletedEventMock = new Mock<ItemDeletedEvent>();
_eventAggregatorMock.Setup(ea => ea.GetEvent<ItemDeletedEvent>())
    .Returns(_itemDeletedEventMock.Object);

_vm = new ItemViewModel(_eventAggregatorMock.Object, */ ... /*);

在我的实际视图模型中,我订阅了事件:

public ItemViewModel(IEventAggregator ea, /* ... */)
{
   _eventAggregator.GetEvent<ItemDeletedEvent>()
      .Subscribe(OnItemDeleted, true);  
}

而且我们从来没有在这里遇到过断点:

public void OnItemDeleted()
{
  // never happens
}

对于我的生活,我无法弄清楚我做错了什么 - 我忽略了一些东西......我是否必须在 Mock 中设置事件的发布事件?我应该使用真正的 ItemDeletedEvent 实例而不是 Mock 吗?任何帮助将不胜感激。

【问题讨论】:

    标签: unit-testing mvvm moq prism autofac


    【解决方案1】:

    => 嗨,斯科特,

    在使用 EventAggregator 时,您要测试 2 个 ViewModel 场景:

    1. 您想要测试您的 ViewModel 是否正在发布事件
    2. 您想要测试您的 ViewModel 在发布事件时是否执行了某些操作。所以 ViewModel 必须订阅该事件才能做某事

    (注意:以下几行适用于 PRISM 的 EventAggregator,我猜你正在使用它。对于其他 EventAggregator,它可能会有所不同)

    对于第一种情况,您必须为事件创建一个模拟。然后,您可以在该模拟实例上验证事件的发布方法是否已被调用。

    对于第二种情况,即您在问题中遇到的情况,您必须在测试中使用真实事件。为什么?

    • 当您在事件模拟上调用 Publish 方法时,该 Publish 方法不会调用该事件的订阅者,因为 Subscribe 方法背后没有逻辑。当然,您可以设置这两种方法并在您的模拟中实现该发布/订阅逻辑。但是没有理由这样做,只需使用真实的 Event
    • 当您使用真实事件时,发布方法将调用所有订阅者。这正是您在测试中所需要的。

    应该是这样的:

    _itemDeletedEvent = new ItemDeletedEvent();
    _eventAggregatorMock.Setup(ea => ea.GetEvent<ItemDeletedEvent>())
      .Returns(_itemDeletedEvent);
    

    现在,您的 ViewModel 将从 EventAggregator 获取此 itemDeletedEvent-instance。在您的测试中,您在此 itemDeletedEvent-instance 上调用 Publish-method,它将起作用。

    我的关于 WPF 和测试驱动开发的 Pluralsight 课程中对此进行了解释:http://www.pluralsight.com/courses/wpf-mvvm-test-driven-development-viewmodels

    托马斯 http://www.thomasclaudiushuber.com

    【讨论】:

    • 您的课程是我编写代码的基础! :-) 谢谢你的帮助,托马斯。
    • 我只是没有得到区别——但我记得你在课程中提到过。有什么方法可以将 EventAggregator 配置为 both
    • 两者都做是什么意思?问题是发布/订阅逻辑不在事件聚合器中,它在事件类中。所以为了测试订阅,你应该使用真实的事件类。如果您不使用真正的 Event 类,而是模拟,那么您需要设置订阅方法并手动存储订阅方法并手动调用它们。
    • 顺便说一句:在您的 ItemViewModel 中,您订阅的是 CurriculumDeletedEvent,而不是 ItemDeletedEvent。
    • 是的,现在一切正常!抱歉耽搁了——我去睡觉了。当我重命名我粘贴的代码中的所有内容时,CurriculumDeletedEvent 引用被忽略了。
    猜你喜欢
    • 2011-08-14
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    相关资源
    最近更新 更多