Reputation: 46008
I have a small system that uses Jonathan Oliver's CommonDomain and EventStore.
How can I unit test my aggregate roots in order to verify that correct events are raised?
Consider following aggregate root:
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;
}
}
I would like to write a following test:
// Arrange
var id = Guid.NewGuid();
var email = "[email protected]";
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
I could set up whole EventStore with memory persistence and synchronous dispatcher, hook up mock event handler and store any published events for verification, but it seems a bit of overkill.
There is an interface IRouteEvents
in CommonDomain. Looks like I could mock it to get the events directly from AggregateBase
but how would I actually pass it to my Subscriber
class? I don't want to 'pollute' my domian with testing-related code.
Upvotes: 2
Views: 2269
Reputation:
I just pushed up NEventStoreExample with code I gathered in various places (StackOverflow, Documently, Greg Young's skillcast).
It's a very basic implementation of NEventStore
that uses CommonDomain
to rebuild aggregate state and an EventSpecification
base test class to test aggregate behaviour.
Upvotes: 3
Reputation: 634
Here is a fairly simple test fixture that uses NUnit and ApprovalTests to test CommonDomain aggregate roots . (ApprovalTests is not required - just kinda makes life simple).
The assumption is that 1) the fixture is instantiated with an aggregate (perhaps already set in a certain state) along with a series of 'given' events to be applied. 2) the test will then invoke a specific command handler as part of the TestCommand method - current expectation is a Func that returns the command that is handled 3) the aggregate snapshot, commands, and events all contain 'rich' ToString methods
The TestCommand method then compares the expected with the approved interactions within the aggregate.
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());
}
}
and an example usage
[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;
});
}
Upvotes: 1
Reputation: 46008
I've found out that AggregateBase
explicitly implements IAggregate
interface, which exposes ICollection GetUncommittedEvents();
method.
So the unit test looks like that:
var eventsRaised = ((IAggregate)subscriber).GetUncommittedEvents();
and no dependency on EventStore is required.
Upvotes: 7