Unit Testing CompositePresentationEvent when using the Dispatcher
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="Dis开发者_开发问答patcher"/>.
/// </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.
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:
- That the code you are testing subscribes at all
- 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.
精彩评论