given-when-then测试fixture定义了三个阶段:配置、执行和验证。每个阶段由不同的接口表示:分别是,FixtureConfiguration, TestExecutor 和 ResultValidator。Fixture类上的静态newGivenWhenThenFixture()方法提供了对其中的第一个类的引用,从而可以提供验证器,等等。
Note:
为了更好地利用这些阶段之间的迁移,最好使用这些方法提供的流式接口,如上面的示例所示。
在配置阶段(即在提供第一个“given”之前),您提供执行测试所需的构建块。作为fixture的一部分提供事件总线、命令总线和事件存储的专用版本。有一些访问器方法来获得对它们的引用。任何命令处理程序不在聚合上直接注册,需要显式地使用registerAnnotatedCommandHandler方法配置。除了带注解的命令处理程序之外,您还可以配置各种各样的组件和设置,以定义如何设置测试的基础设施。
一旦配置了fixture,您就可以定义“jiven”事件了。测试fixture将把这些事件包装为DomainEventMessage。如果“given”事件实现了Message,则该消息的有效负载和元数据将包含在DomainEventMessage中,否则将使用给定的事件作为有效负载。DomainEventMessage的序列号是顺序的,从0开始。
或者,您也可以为“given”场提供命令。在这种情况下,当在测试中执行实际的命令时,这些命令生成的事件将被用于事件源。使用“givenCommands(…)“提供命令对象的。
执行阶段允许您提供命令处理组件执行的命令。被调用的处理程序(无论是在聚合的还是作为外部处理程序)的行为被监控,并与验证阶段注册的预计结果相比较。
Note:
在测试的执行过程中,Axon试图检测在测试的聚合中存在的任何非法状态变化。它通过将命令执行后的聚合状态与聚合的状态进行比较,如果它来自所有“given”和存储的事件。如果该状态不相同,这意味着状态更改发生在聚合的事件处理程序方法之外。在比较中,static和transient 字段被忽略,因为它们通常包含对资源的引用。
可以使用setReportIllegalStateChange方法在fixture的配置中切换检测。
最后一个阶段是验证阶段,允许你检查命令处理组件的活动。这完全是根据返回值和事件来完成的。
测试fixture允许您验证命令处理程序的返回值。您可以显式地定义预期的返回值,或者简单地要求方法成功返回。您还可以表示您期望CommandHandler抛出的任何异常。
另一个组件是已发布事件的验证。有两种匹配预期事件的方法。
第一是通过事件实例,它需要与实际的事件是行逐字的比较。将预期事件的所有属性与实际事件中的对应对象进行比较(使用equals())。如果其中一个属性不相等,则测试失败,并生成一个广泛的错误报告。
表达期望的另一种方式是使用的匹配器(Hamcrest库提供的)。匹配器接口规定了两个方法matches(Object)和describeTo(Description)。第一个返回一个布尔值,指示是否匹配或不匹配。第二个让你表达你的期望。例如,一个“GreaterThanTwoMatcher”可以添加“任何值大于2的事件“的描述。描述允许创建关于测试用例失败的错误消息.
创建事件列表的匹配器可能是繁琐和容易出错的工作。为了简化问题,Axon提供了一组匹配器允许你提供一组特定于事件的匹配器,并告诉Axon应该如何匹配列表。
下面是可用的事件列表匹配器和他们的目的的概述:
>List with all of: Matchers.listWithAllOf(event matchers...)
如果所有提供的事件都匹配在实际事件列表中的至少一个事件,这个matcher将会成功。不管是否有多个匹配器匹配相同的事件,或如果列表中一个事件不匹配任何匹配器。
>List with any of: Matchers.listWithAnyOf(event matchers...)
如果一个或多个事件匹配器与实际的事件列表中一个或多个事件匹配,该匹配器将成功。一些匹配器甚至一个也不匹配,而另一个匹配多个。
>Sequence of Events: Matchers.sequenceOf(event matchers...)
使用此匹配器来验证实际事件匹配器和提供的事件匹配器有相同的顺序。如果匹配器与后一个事件相匹配,与前一个匹配器匹配的事件相匹配,该匹配器将成功。这意味着可能出现不匹配事件的“gaps”。
>Exact sequence of Events: Matchers.exactSequenceOf(event matchers...)
“事件的序列”匹配器的变化不允许不匹配事件的空隙。这意味着每个匹配器必须与事件后面的事件相匹配,与前一个匹配器匹配的事件相匹配。每个匹配器都应该与它前一个匹配器相对应的事件的后续一个事件相匹配.
为了方便起见,提供了一些普遍需要的事件匹配器。他们与单个事件实例相匹配:
>Equal Event: Matchers.equalTo(instance...)
验证given对象在语义上等于given事件,这个匹配器将比较实际和预期的对象的所有字段的值使用一个null-safe相等方法。这意味着可以比较事件,即使它们不实现equals方法。存储在given参数字段上的对象用equals进行比较,要求他们正确实现。
>No More Events: Matchers.andNoMore() or Matchers.nothing()
仅与空值匹配,这个匹配器可以作为最后一个匹配器添加到事件的准确顺序匹配器,以确保没有不匹配的事件依然存在。
由于匹配器传递一个事件消息列表,有时你只是想验证消息的有效负载。有匹配器来帮助你:
>Payload Matching: Matchers.messageWithPayload(payload matcher)
验证消息的有效负载匹配给定的有效载荷匹配器。
>Payloads Matching: Matchers.payloadsMatching(list matcher)
验证消息的有效负载匹配给定的有效载荷匹配器。给定的匹配器必须匹配列表包含的每个消息的有效负载。有效负载匹配匹配器通常用作外匹配器,以防止重复有效负载匹配器。
下面是一个简单的代码示例,以显示这些匹配器的使用。在这个例子中,我们预期共有两个事件发布。第一个事件必须是一个“ThirdEvent”,第二个是“aFourthEventWithSomeSpecialThings”。可能没有第三个事件,因为那样"andNoMore"匹配器会失败。
fixture.given(new FirstEvent(), new SecondEvent())
.when(new DoSomethingCommand("aggregateId"))
.expectEventsMatching(exactSequenceOf(
// we can match against the payload only:
messageWithPayload(equalTo(new ThirdEvent())),
// this will match against a Message
aFourthEventWithSomeSpecialThings(),
// this will ensure that there are no more events
andNoMore()
));
// or if we prefer to match on payloads only:
.expectEventsMatching(payloadsMatching(
exactSequenceOf(
// we only have payloads, so we can equalTo directly
equalTo(new ThirdEvent()),
// now, this matcher matches against the payload too
aFourthEventWithSomeSpecialThings(),
// this still requires that there is no more events
andNoMore()
)
));