Подтвердить что ты не робот

Как проверить подписку агрегатора событий Prism на UIThread?

У меня есть класс, который подписывается на событие через агрегатор событий PRISM.

Как несколько сложно издеваться над агрегатором событий, как указано здесь, я просто создаю реальный экземпляр и передаю его в тестируемую систему.

В моем тесте я затем публикую событие через этот агрегатор, а затем проверяю, как реагирует моя тестируемая система. Поскольку событие будет создано файловым сервером во время производства, я хочу использовать автоматическую отправку, подписавшись на UIThread, поэтому я могу обновить свой интерфейс после возникновения события.

Проблема заключается в том, что во время теста событие никогда не замечается в тестируемой системе, если я не подписываюсь на UIThread.

Я использую MSpec для своих тестов, которые я запускаю из VS2008 через TDD.Net. Добавление [RequiresSta] в мой тестовый класс не помогло

Есть ли у кого-нибудь решение, которое избавляет меня от изменения ThreadOption во время моих тестов (например, через свойство - что уродливое взломать)???

4b9b3361

Ответ 1

Если вы издеваетесь над событием и агрегатором событий и используете обратный вызов moq, вы можете это сделать.

Вот пример:

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 thread
// instead of the UI thread
callback.Invoke(new MyEventArgs(someObject));

// Assert

Ответ 2

Я действительно думаю, что вы должны использовать mocks для всего, а не 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);

Ответ 3

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

Опять же, если ваши чувства означают, что вы хотите использовать насмешки, пойдите для этого. Если вы предпочитаете использовать настоящую вещь, это работает.