Вам может не понравиться это, так как это может включать в себя то, что вы считаете "безобразным хаком", но я предпочитаю использовать настоящий EventAggregator, а не дразнить все.Хотя якобы внешний ресурс, EventAggregator работает в памяти и поэтому не требует большой настройки, очистки и не является узким местом, как другие внешние ресурсы, такие как базы данных, веб-сервисы и т. Д., И поэтому я чувствую этоподходит для использования в модульном тесте.Исходя из этого, я использовал этот метод для преодоления проблемы потока пользовательского интерфейса в NUnit с минимальными изменениями или риском для моего производственного кода ради тестов.
Сначала я создал метод расширения, например, так:
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>
Опять же, если ваши чувства означают, что вы хотите использовать макеты, сделайте это.Если вы предпочитаете использовать настоящую вещь, это работает.