Я не понимаю разницы в этих модульных тестах

Я использую Moq, xUnit и Prism 4. Цель моего модульного теста — запустить событие и подтвердить, что свойство изменилось в моей модели представления, чтобы соответствовать значению из события. Этот тест, кстати, не проходит (Ожидаемое: 5, фактическое: 0):

// Version One
[Fact]
public void Should_set_DayCount_on_DayCountChangedEvent()
{
    var eaMock = new Mock<IEventAggregator>();
    eaMock.SetupCurriculumEvents(); // see below
    var vm = new CurriculumItemViewModel(eaMock.Object, _systemStatus.Object);
    vm.Load(_newItem);
    var dayCount = 5;

    eaMock.Object.GetEvent<DayCountChangedNotification>().Publish(dayCount);

    Assert.Equal(dayCount, _vm.DayCount);
}

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

public static void SetupCurriculumEvents(this Mock<IEventAggregator> eaMock)
{
     eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
         .Returns(new DayCountChangedNotification());

     // lots of other "notification" events here as well
}

Затем я понял, что создаю новое событие каждый раз, когда оно извлекается из агрегатора фиктивных событий, поэтому Subscribe() в одном экземпляре (в виртуальной машине) не находится в том же экземпляре, что и Publish(dayCount) в моем тесте.

Что ж, думаю, давайте всегда будем использовать один и тот же объект (путем перезаписи Setup() метода расширения для этого события), и у нас все получится:

// Version Two
[Fact]
public void Should_set_DayCount_on_DayCountChangedEvent()
{
    var dayCountChangedEvent = new DayCountChangedNotification();
    var eaMock = new Mock<IEventAggregator>();
    eaMock.SetupCurriculumEvents(); // still need this for all the other events
    // overwrite the setup from the extension method
    eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
        .Returns(dayCountChangedEvent);

    var vm = new CurriculumItemViewModel(eaMock.Object, _systemStatus.Object);
    vm.Load(_newItem);

    var dayCount = 5;

    dayCountChangedEvent.Publish(dayCount);

    Assert.Equal(dayCount, _vm.DayCount);
}

... который также эффектно терпит неудачу.

По какой-то причине я решил попробовать рефакторинг метода расширения (и вернул модульный тест обратно к первой версии):

public static class MockingExtensions
{
    private static DayCountChangedNotification DayNotification = new DayCountChangedNotification();

    public static void SetupCurriculumEvents(this Mock<IEventAggregator> eaMock)
    {
         eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
         .Returns(DayNotification);

         // etc...
    }
}

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

Вот в чем проблема: этот тест пройден.

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

Принятый ответ должен объяснить две вещи:

  1. Почему переработанный метод расширения со статическим экземпляром проходит?
  2. Почему вторая версия не проходит?

person Scott Baker    schedule 06.05.2016    source источник
comment
Объявление поля статического класса — это не то же самое, что объявление локальной переменной. Вы запускали только этот тест или вы запускали этот тест во время тестового прогона с другими тестами? Какова реализация DayCountChangedNotification?   -  person Jeroen Heier    schedule 07.05.2016
comment
DayCountChangedNotification — это просто пустой класс, наследуемый от класса CompositePresentationEvent<int?> Prism. Тест выполнялся как в одиночку, так и совместно с другими тестами — результаты были одинаковыми.   -  person Scott Baker    schedule 09.05.2016


Ответы (1)


Я попытался воспроизвести вашу проблему и создал код ниже. Все три теста прошли успешно, поэтому я думаю, что в вопросе чего-то не хватает. Важно знать, что moqObject.Setup(...).Return(true) — это не то же самое, что moqObject.Setup(...).Return(() => true). Дополнительную информацию см. здесь

 namespace Test
 {
    using Microsoft.Practices.Prism.Events;
    using Moq;
    using System;
    using Xunit;

    // Moq    4.2.1510.2205
    // Prism  4.0.0.0
    // xunit  2.1.0

    public class CurriculumItemViewModel
    {
        public CurriculumItemViewModel(IEventAggregator agg)
        {
            agg.GetEvent<DayCountChangedNotification>().Subscribe((int? x) => {
                DayCount = x.Value;
            });
        }

        public int DayCount { get; set; }
    }

    public class DayCountChangedNotification : CompositePresentationEvent<int?>
    {
        public DateTime Created = DateTime.Now;
    }

    public static class Extensions
    {
        private static DayCountChangedNotification DayNotification = new DayCountChangedNotification();

        public static void SetupCurriculumEventsV1(this Mock<IEventAggregator> eaMock)
        {
            eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
                  .Returns(new DayCountChangedNotification());
        }

        public static void SetupCurriculumEventsV2(this Mock<IEventAggregator> eaMock)
        {
            eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
                  .Returns(DayNotification);
        }
    }

    public class TestClass
    {     
        [Fact]
        public void ShouldSetDayCountOnDayCountChangedEvent1a()
        {
            // Arrange
            var dayCount = 5;
            var eaMock = new Mock<IEventAggregator>();
            eaMock.SetupCurriculumEventsV1();

            var vm = new CurriculumItemViewModel(eaMock.Object);
            var notification = eaMock.Object.GetEvent<DayCountChangedNotification>();
            var notification2 = eaMock.Object.GetEvent<DayCountChangedNotification>();

            // Act
            notification.Publish(dayCount);

            // Assert
            Assert.Equal(dayCount, vm.DayCount);
        }

        [Fact]
        public void ShouldSetDayCountOnDayCountChangedEvent2()
        {
            // Arrange
            var dayCount = 5;
            var eaMock = new Mock<IEventAggregator>();
            eaMock.SetupCurriculumEventsV1();

            // This will override the setup done by SetupCurriculumEventsV1
            var notification = new DayCountChangedNotification();         
            eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
                  .Returns(notification);

            var vm = new CurriculumItemViewModel(eaMock.Object);

            // Act
            notification.Publish(dayCount);

            // Assert
            Assert.Equal(dayCount, vm.DayCount);
        }

        [Fact]
        public void ShouldSetDayCountOnDayCountChangedEvent1b()
        {
            // Arrange
            var dayCount = 5;
            var eaMock = new Mock<IEventAggregator>();

            eaMock.SetupCurriculumEventsV2(); 

            var vm = new CurriculumItemViewModel(eaMock.Object);
            var notification = eaMock.Object.GetEvent<DayCountChangedNotification>();

            // Act
            notification.Publish(dayCount);

            // Assert
            Assert.Equal(dayCount, vm.DayCount);            
        }
    }
}
person Jeroen Heier    schedule 11.05.2016
comment
Отличный ответ - дайте мне несколько дней, чтобы разобраться с ним, и я дам вам знать. - person Scott Baker; 13.05.2016