使用 Dispatcher 时对 CompositePresentationEvent 进行单元测试

发布于 2024-10-15 05:04:12 字数 1333 浏览 6 评论 0原文

我使用 Prism/Composite Application Library 并尝试对一些使用 EventAggregator 订阅 CompositePresentationEvent 的代码进行单元测试。引发该事件的代码正在另一个线程上引发该事件,因此我使用 ThreadOption.UIThread 订阅该事件。

当事件引发回调时,它使用应用程序调度程序将其放入 UI 线程。这在正常执行期间没问题,但在单元测试期间没有调度程序。 CompositePresentationEvent 中的代码如下所示:

    private IDispatcherFacade UIDispatcher
    {
        get
        {
            if (uiDispatcher == null)
            {
                this.uiDispatcher = new DefaultDispatcher();
            }

            return uiDispatcher;
        }
    }



public class DefaultDispatcher : IDispatcherFacade
{
    /// <summary>
    /// Forwards the BeginInvoke to the current application's <see cref="Dispatcher"/>.
    /// </summary>
    /// <param name="method">Method to be invoked.</param>
    /// <param name="arg">Arguments to pass to the invoked method.</param>
    public void BeginInvoke(Delegate method, object arg)
    {
        if (Application.Current != null)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, arg);
        }
    }
}

问题在于 CompositePresentationEvent 与 DefaultDispatcher 绑定,如果没有应用程序运行,则该调度程序不执行任何操作。

有没有人在这种情况下进行过成功的单元测试?有什么技巧或解决方法可以让调度员投入使用吗?

我知道我可以将回调设置为内部并允许我的单元测试调用此方法,但我不想更改我的代码并将此方法作为最后的手段。

Im using the Prism/Composite Application Library and trying to unit test some code that subscribes to a CompositePresentationEvent using the EventAggregator. The code that is raising the event is raising it on another thread so I am subscribing to the event using ThreadOption.UIThread.

When the event raises the callback it uses the application dispatcher to put it onto the UI thread. This is fine during normal execution but during a unit test there is no dispatcher. The code in CompositePresentationEvent looks like this:

    private IDispatcherFacade UIDispatcher
    {
        get
        {
            if (uiDispatcher == null)
            {
                this.uiDispatcher = new DefaultDispatcher();
            }

            return uiDispatcher;
        }
    }



public class DefaultDispatcher : IDispatcherFacade
{
    /// <summary>
    /// Forwards the BeginInvoke to the current application's <see cref="Dispatcher"/>.
    /// </summary>
    /// <param name="method">Method to be invoked.</param>
    /// <param name="arg">Arguments to pass to the invoked method.</param>
    public void BeginInvoke(Delegate method, object arg)
    {
        if (Application.Current != null)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, arg);
        }
    }
}

The problem being that CompositePresentationEvent is tied to the DefaultDispatcher, and this dispatcher does nothing if there is no Application running.

Has anyone had any success unit testing in this kind of situation? Any tips or workarounds to kick the dispatcher into life?

I know I could make my callback internal and allow my unit test to call this method but I would prefer not to change my code and leave this approach as a last resort.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

绻影浮沉 2024-10-22 05:04:12

您没有发布您的测试,所以我不清楚您要测试什么,但很可能您正在尝试测试以下内容之一:

  1. 您正在测试的代码完全订阅
  2. 您正在测试的代码对事件做出适当的反应

无论哪种情况,您都需要模拟 EventAggregator。因为事件不是您想要测试的内容,而是使用它的代码,所以您想要提供一个虚假的替代方案来完成您想要它做的事情。我会尽力提供一个很好的例子。我使用 Moq,但你可以选择你喜欢的任何模拟框架。

在此测试中,我只是断言在构造函数中调用了 Subscribe,但如果您想测试类对引发事件的反应,您的测试可能会更复杂。该测试显示一个 CompositePresentationEvent

//Arrange
Mock<MyEvent> mockEvent = new Mock<MyEvent>();
Mock<IEventAggregator> mockAggregator = new Mock<IEventAggregator>();

mockEvent.Setup
(
     evnt => evnt.Subscribe(It.IsAny<Action<int>>())
);
mockAggregator.Setup
(
    agg => agg.GetEvent<MyEvent>()
              .Returns(mockEvent.Object);
);

//Act
MyClassIWantToTest target = new MyClassIWantToTest(mockAggregator.Object);

//Assert
mockEvent.VerifyAll();

这就是基础知识。这里的经验法则是,如果您的测试依赖于难以提供的系统资源,请将其与测试隔离。

编辑:阅读您的问题后,我发现您正在尝试测试回调。

在本示例中,我测试“CurrentValueProperty”属性是否设置为回调方法中传递的任何值。这是该示例:

//Arrange
Mock<MyEvent> mockEvent = new Mock<MyEvent>();
Mock<IEventAggregator> mockAggregator = new Mock<IEventAggregator>();
Action<int> theEventCallback = null;

mockEvent.Setup
(
    evnt => evnt.Subscribe(It.IsAny<Action<int>>())
)
.Callback<Action<int>>
(
    cb => theEventCallback = cb
);


mockAggregator.Setup
(
    agg => agg.GetEvent<MyEvent>()
)
.Returns(mockEvent.Object);

//Act
MyClassIWantToTest target = new MyClassIWantToTest(mockAggregator.Object);

//we expect this to be populated by the callback specified in our mock setup
//that will be triggered when Subscribe is called in 
//MyClassIWantToTest's constructor
theEventCallback(27);

//Assert
Assert.AreEqual(target.CurrentValueProperty, 27);

就是这样。

You didn't post your tests, so I'm unclear what you are trying to test, but most-likely you are trying to test one of the following things:

  1. That the code you are testing subscribes at all
  2. That the code you are testing reacts to events appropriately

In either case, you are going to want to mock the EventAggregator. Because the Event isn't what you want to test, but rather the code that utilizes it, you want to provide a fake alternative that does what you want it to do. I'll try and provide a good example. I use Moq, but you can choose whatever mocking framework you like.

In this test, I simply assert that Subscribe was called in the constructor, but your test might be more complicated if you are wanting to test the class's reaction to a raised event. The test shows a CompositePresentationEvent<int>.

//Arrange
Mock<MyEvent> mockEvent = new Mock<MyEvent>();
Mock<IEventAggregator> mockAggregator = new Mock<IEventAggregator>();

mockEvent.Setup
(
     evnt => evnt.Subscribe(It.IsAny<Action<int>>())
);
mockAggregator.Setup
(
    agg => agg.GetEvent<MyEvent>()
              .Returns(mockEvent.Object);
);

//Act
MyClassIWantToTest target = new MyClassIWantToTest(mockAggregator.Object);

//Assert
mockEvent.VerifyAll();

That's the basics. The rule of thumb here is that if you tests rely on a system resource that is difficult to provide, isolate it from the test.

Edit: after reading your question I see you are trying to test the callback.

In this sample, I test if the "CurrentValueProperty" property is set to whatever value is passed in the callback method. Here is that sample:

//Arrange
Mock<MyEvent> mockEvent = new Mock<MyEvent>();
Mock<IEventAggregator> mockAggregator = new Mock<IEventAggregator>();
Action<int> theEventCallback = null;

mockEvent.Setup
(
    evnt => evnt.Subscribe(It.IsAny<Action<int>>())
)
.Callback<Action<int>>
(
    cb => theEventCallback = cb
);


mockAggregator.Setup
(
    agg => agg.GetEvent<MyEvent>()
)
.Returns(mockEvent.Object);

//Act
MyClassIWantToTest target = new MyClassIWantToTest(mockAggregator.Object);

//we expect this to be populated by the callback specified in our mock setup
//that will be triggered when Subscribe is called in 
//MyClassIWantToTest's constructor
theEventCallback(27);

//Assert
Assert.AreEqual(target.CurrentValueProperty, 27);

That's it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文