我有一个课程,通过PRISMs事件聚合器订阅一个事件。
由于有点难以模拟事件聚合器here,我只是实例化一个真实聚合器并将其传递给被测系统。
在我的测试中,我然后通过该聚合器发布事件,然后检查我的测试系统如何对其做出反应。由于事件将在生产期间由FileSystemWatcher引发,我想通过订阅UIThread来使用自动分派,因此我可以在引发事件后更新我的UI。
问题是,在测试期间,除非我没有订阅UIThread,否则事件永远不会在被测系统中被注意到。
我正在使用MSpec进行测试,我通过TDD.Net从VS2008内部运行。
将[RequiresSta]
添加到我的测试类没有帮助
有没有人有解决方案,这使我免于在测试期间更改ThreadOption(例如通过属性 - 这是一个多么丑陋的黑客攻击)???
答案 0 :(得分:18)
如果您同时模拟事件和Event Aggregator,并使用moq的Callback,则可以执行此操作。
以下是一个例子:
Mock<IEventAggregator> mockEventAggregator;
Mock<MyEvent> mockEvent;
mockEventAggregator.Setup(e => e.GetEvent<MyEvent>()).Returns(mockEvent.Object);
// Get a copy of the callback so we can "Publish" the data
Action<MyEventArgs> callback = null;
mockEvent.Setup(
p =>
p.Subscribe(
It.IsAny<Action<MyEventArgs>>(),
It.IsAny<ThreadOption>(),
It.IsAny<bool>(),
It.IsAny<Predicate<MyEventArgs>>()))
.Callback<Action<MyEventArgs>, ThreadOption, bool, Predicate<MyEventArgs>>(
(e, t, b, a) => callback = e);
// Do what you need to do to get it to subscribe
// Callback should now contain the callback to your event handler
// Which will allow you to invoke the callback on the test's thread
// instead of the UI thread
callback.Invoke(new MyEventArgs(someObject));
// Assert
答案 1 :(得分:15)
我真的认为你应该为所有事情使用模拟而不是EventAggregator。根本不嘲笑......我不认为链接的答案证明了EventAggregator的可测试性。
这是你的考试。我不使用MSpec,但这是Moq的测试。您没有提供任何代码,因此我将其基于链接代码。你的场景比链接场景有点困难,因为另一个OP只是想知道如何验证订阅被调用,但你实际上想要调用订阅中传递的方法...更难,但不是非常
//Arrange!
Mock<IEventAggregator> eventAggregatorMock = new Mock<IEventAggregator>();
Mock<PlantTreeNodeSelectedEvent> eventBeingListenedTo = new Mock<PlantTreeNodeSelectedEvent>();
Action<int> theActionPassed = null;
//When the Subscribe method is called, we are taking the passed in value
//And saving it to the local variable theActionPassed so we can call it.
eventBeingListenedTo.Setup(theEvent => theEvent.Subscribe(It.IsAny<Action<int>>()))
.Callback<Action<int>>(action => theActionPassed = action);
eventAggregatorMock.Setup(e => e.GetEvent<PlantTreeNodeSelectedEvent>())
.Returns(eventBeingListenedTo.Object);
//Initialize the controller to be tested.
PlantTreeController controllerToTest = new PlantTreeController(eventAggregatorMock.Object);
//Act!
theActionPassed(3);
//Assert!
Assert.IsTrue(controllerToTest.MyValue == 3);
答案 2 :(得分:5)
你可能不喜欢这个,因为它可能涉及你觉得“丑陋的黑客”,但我的偏好是使用真正的EventAggregator而不是嘲笑一切。虽然表面上是一个外部资源,但EventAggregator在内存中运行,因此不需要太多的设置,清理,并且不像其他外部资源(如数据库,Web服务等)那样瓶颈,因此我觉得它适合在单元测试中使用。在此基础上,我使用此方法来克服NUnit中的UI线程问题,为了测试,我的生产代码只有很小的变化或风险。
首先,我创建了一个类似的扩展方法:
public static class ThreadingExtensions
{
private static ThreadOption? _uiOverride;
public static ThreadOption UiOverride
{
set { _uiOverride = value; }
}
public static ThreadOption MakeSafe(this ThreadOption option)
{
if (option == ThreadOption.UIThread && _uiOverride != null)
return (ThreadOption) _uiOverride;
return option;
}
}
然后,在我的所有活动订阅中,我使用以下内容:
EventAggregator.GetEvent<MyEvent>().Subscribe
(
x => // do stuff,
ThreadOption.UiThread.MakeSafe()
);
在生产代码中,这只是无缝地工作。出于测试目的,我所要做的就是在我的设置中添加这个,并在我的测试中添加一些同步代码:
[TestFixture]
public class ExampleTest
{
[SetUp]
public void SetUp()
{
ThreadingExtensions.UiOverride = ThreadOption.Background;
}
[Test]
public void EventTest()
{
// This doesn't actually test anything useful. For a real test
// use something like a view model which subscribes to the event
// and perform your assertion on it after the event is published.
string result = null;
object locker = new object();
EventAggregator aggregator = new EventAggregator();
// For this example, MyEvent inherits from CompositePresentationEvent<string>
MyEvent myEvent = aggregator.GetEvent<MyEvent>();
// Subscribe to the event in the test to cause the monitor to pulse,
// releasing the wait when the event actually is raised in the background
// thread.
aggregator.Subscribe
(
x =>
{
result = x;
lock(locker) { Monitor.Pulse(locker); }
},
ThreadOption.UIThread.MakeSafe()
);
// Publish the event for testing
myEvent.Publish("Testing");
// Cause the monitor to wait for a pulse, but time-out after
// 1000 millisconds.
lock(locker) { Monitor.Wait(locker, 1000); }
// Once pulsed (or timed-out) perform your assertions in the real world
// your assertions would be against the object your are testing is
// subscribed.
Assert.That(result, Is.EqualTo("Testing"));
}
}
为了使等待和脉冲更简洁,我还在ThreadingExtensions中添加了以下扩展方法:
public static void Wait(this object locker, int millisecondTimeout)
{
lock (locker)
{
Monitor.Wait(locker);
}
}
public static void Pulse(this object locker)
{
lock (locker)
{
Monitor.Pulse(locker);
}
}
然后我可以做:
// <snip>
aggregator.Subscribe(x => locker.Pulse(), ThreadOption.UIThread.MakeSafe());
myEvent.Publish("Testing");
locker.Wait(1000);
// </snip>
同样,如果你的敏感意味着你想要使用模拟,那就去吧。如果您更愿意使用真实的东西,那就可以了。