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;
});
}