CommonDomain - как протестировать агрегатный корень

У меня есть небольшая система, которая использует CommonDomain и EventStore Джонатана Оливера.

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

Рассмотрим следующий совокупный корень:

public class Subscriber : AggregateBase
{
        private Subscriber(Guid id)
        {
            this.Id = id;
        }

        private Subscriber(Guid id, string email, DateTimeOffset registeredDate)
            : this(id)
        {
            this.RaiseEvent(new NewSubscriberRegistered(this.Id, email, registeredDate));
        }

        public string Email{ get; private set; }
        public DateTimeOffset RegisteredDate { get; private set; }

        public static Subscriber Create(Guid id, string email, DateTimeOffset registeredDate)
        {
            return new Subscriber(id, email, registeredDate);
        }

        private void Apply(NewSubscriberRegistered @event)
        {
            this.Email = @event.Email;
            this.RegisteredDate = @event.RegisteredDate;
        }
}

Я хотел бы написать следующий тест:

    // Arrange
    var id = Guid.NewGuid();
    var email = "test@thelightfull.com";
    var registeredDate = DateTimeOffset.Now;

    // Act
    var subscriber = Subscriber.Create(id, email, registeredDate);

    // Assert
    var eventsRaised = subscriber.GetEvents();  <---- How to get the events?
    // Assert that NewSubscriberRegistered event was raised with valid data

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

Есть интерфейс IRouteEvents в CommonDomain. Похоже, я мог бы высмеять это, чтобы получить события непосредственно от AggregateBase но как бы я на самом деле передать его моему Subscriber учебный класс? Я не хочу "загрязнять" мой домиан кодом, связанным с тестированием.

3 ответа

Решение

Я узнал, что AggregateBase явно реализует IAggregate интерфейс, который выставляет ICollection GetUncommittedEvents(); метод.

Итак, юнит-тест выглядит так:

var eventsRaised = ((IAggregate)subscriber).GetUncommittedEvents();

и никакой зависимости от EventStore не требуется.

Я только что добавил NEventStoreExample с кодом, который собирал в разных местах ( Stackru, Documently, Gcast Young's skillcast).

Это очень простая реализация NEventStore который использует CommonDomain восстановить совокупное состояние и EventSpecification базовый тестовый класс для тестирования агрегатного поведения.

Вот довольно простое тестовое устройство, которое использует NUnit и ApprovalTests для тестирования агрегатных корней CommonDomain. (ApprovalTests не требуется - просто делает жизнь проще).

Предполагается, что 1) объект создается с помощью агрегата (возможно, уже установленного в определенном состоянии) вместе с серией "заданных" событий, которые должны быть применены. 2) тест затем вызовет определенный обработчик команды как часть метода TestCommand - текущим ожиданием является Func, который возвращает обработанную команду 3) совокупный снимок, команды и события содержат "богатые" методы ToString

Затем метод TestCommand сравнивает ожидаемое с утвержденными взаимодействиями в совокупности.

    public class DomainTestFixture<T>
        where T : AggregateBase
    {
        private readonly T _agg;
        private readonly StringBuilder _outputSb = new StringBuilder();

        public DomainTestFixture(T agg, List<object> giveEvents)
        {
            _agg = agg;
            _outputSb.AppendLine(string.Format("Given a {0}:", agg.GetType().Name));

            giveEvents.ForEach(x => ((IAggregate) _agg).ApplyEvent(x));

            _outputSb.AppendLine(
                giveEvents.Count == 0
                    ? string.Format("with no previously applied events.")
                    : string.Format("with previously applied events:")
                );
            giveEvents.ForEach(x => _outputSb.AppendLine(string.Format(" - {0}", x)));


            ((IAggregate) _agg).ClearUncommittedEvents();

            var snapshot = ((IAggregate) _agg).GetSnapshot();
            _outputSb.AppendLine(string.Format("which results in the state: {0}", snapshot));
        }

        public void TestCommand(Func<T, object> action)
        {
            var cmd = action.Invoke(_agg);
            _outputSb.AppendLine(string.Format("When handling the command: {0}", cmd));

            _outputSb.AppendLine(string.Format("Then the {0} reacts ", _agg.GetType().Name));
            var raisedEvents = ((IAggregate) _agg).GetUncommittedEvents().Cast<object>().ToList();

            _outputSb.AppendLine(
                raisedEvents.Count == 0
                    ? string.Format("with no raised events")
                    : string.Format("with the following raised events:")
                );

            raisedEvents.ForEach(x => _outputSb.AppendLine(string.Format(" - {0}", x)));

            var snapshot = ((IAggregate) _agg).GetSnapshot();
            var typ = snapshot.GetType();

            _outputSb.AppendLine(string.Format("and results in the state: {0}", snapshot));

            Approvals.Verify(_outputSb.ToString());

            Assert.Pass(_outputSb.ToString());
        }
    }

и пример использования

    [Test]
    public void Test_Some_Aggregate_Handle_Command()
    {
        var aggId = Guid.Empty;
        var tester = new DomainTestFixture<PartAggregate>(
            new PartAggregate(aggId, null),
            new List<object>()
            {
                new PartOrdered(),
                new PartReceived()
            }
            );
        tester.TestCommand(
            (agg) =>
                {
                    var cmd = new RejectPart();
                    agg.Handle(cmd);
                    return cmd;
                });
    }
Другие вопросы по тегам