Модульное тестирование CompositePresentationEvent при использовании Dispatcher

Я использую библиотеку приложений Prism/Composite и пытаюсь протестировать некоторый код, который подписывается на CompositePresentationEvent, используя EventAggregator. Код, вызывающий событие, вызывает его в другом потоке, поэтому я подписываюсь на событие с помощью ThreadOption.UIThread.

Когда событие вызывает обратный вызов, оно использует диспетчер приложений, чтобы поместить его в поток пользовательского интерфейса. Это нормально во время обычного выполнения, но во время модульного теста диспетчера нет. Код в 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, и этот диспетчер ничего не делает, если приложение не запущено.

У кого-нибудь было успешное модульное тестирование в такой ситуации? Любые советы или обходные пути, чтобы оживить диспетчера?

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


person Scott    schedule 27.01.2011    source источник
comment
если вы пытаетесь провести модульное тестирование своего класса (не CompositePresentationEvent), то введение реального экземпляра CompositePresentationEvent в ваш тест делает их немодульными. Может быть, вместо того, чтобы играть с CPE, вам следует изолировать от него свой класс, а затем использовать mocks/stubs?   -  person Snowbear    schedule 28.01.2011
comment
Да справедливое замечание. Я думаю, что немного увяз в проблемах, представленных этими классами, когда я должен быть в состоянии издеваться над ними. Я попробую предложение Андерсона и посмотрю, решит ли оно это. Спасибо!   -  person Scott    schedule 28.01.2011


Ответы (1)


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

  1. Что код, который вы тестируете, вообще подписывается
  2. Что код, который вы тестируете, правильно реагирует на события.

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

В этом тесте я просто утверждаю, что в конструкторе был вызван метод Subscribe, но ваш тест может быть более сложным, если вы хотите проверить реакцию класса на инициированное событие. Тест показывает 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();

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

Редактировать: после прочтения вашего вопроса я вижу, что вы пытаетесь протестировать обратный вызов.

В этом примере я проверяю, установлено ли для свойства 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);

Вот и все.

person Anderson Imes    schedule 27.01.2011
comment
Вы правы с номером 2 с точки зрения того, чего я пытаюсь достичь. Я не могу использовать фиктивную структуру по определенным причинам, но у меня уже есть фиктивный агрегатор событий, и я должен иметь возможность расширить его, чтобы имитировать событие презентации. Спасибо, попробую! - person Scott; 28.01.2011
comment
Я смог издеваться над событиями презентации, которые я использовал, используя аналогичный подход, хотя было бы лучше с насмешливой структурой - person Scott; 28.01.2011
comment
@Scott: мне было бы интересно, почему вы не можете использовать насмешливую структуру. Просто любопытно. - person Anderson Imes; 28.01.2011