Domanda

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 = "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

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.

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

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;
                });
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top