【问题标题】:CommonDomain - how to unit test aggregate rootCommonDomain - 如何对聚合根进行单元测试
【发布时间】:2020-10-27 18:26:09
【问题描述】:

我有一个使用 Jonathan Oliver 的 CommonDomainEventStore 的小系统。

如何对聚合根进行单元测试以验证是否引发了正确的事件?

考虑以下聚合根:

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,连接模拟事件处理程序并存储任何已发布的事件以供验证,但这似乎有点矫枉过正。

CommonDomain 中有一个接口IRouteEvents。看起来我可以模拟它以直接从 AggregateBase 获取事件,但我如何将它实际传递给我的 Subscriber 类?我不想用与测试相关的代码“污染”我的领域。

【问题讨论】:

  • 可能值得测试更高级别(命令处理程序)。观看我的博客,了解关于这个主题的未来帖子(尽管不是特定于 CommonDomain)。

标签: unit-testing cqrs event-sourcing neventstore commondomain


【解决方案1】:

我发现AggregateBase 显式实现了IAggregate 接口,该接口暴露了ICollection GetUncommittedEvents(); 方法。

所以单元测试看起来像这样:

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

并且不需要依赖 EventStore。

【讨论】:

  • 谢谢,这正是我所需要的。
【解决方案2】:

我刚刚用我在各个地方收集的代码(StackOverflowDocumentlyGreg Young's skillcast)推送了NEventStoreExample

这是NEventStore 的一个非常基本的实现,它使用CommonDomain 来重建聚合状态,并使用EventSpecification 基础测试类来测试聚合行为。

【讨论】:

    【解决方案3】:

    这是一个相当简单的测试夹具,它使用 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;
                    });
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-08
      • 2019-11-02
      • 1970-01-01
      • 1970-01-01
      • 2016-09-27
      • 1970-01-01
      • 2013-09-30
      相关资源
      最近更新 更多