如何使用axon-framework对域模型进行单元测试

Yug*_*hou 1 unit-testing domain-driven-design cqrs event-sourcing axon

我最近在学习CQRS,所以我开始了一个带有axon-framework(Java CRQS框架)的示例项目.

根据快速入门,我得到了以下内容:

public class CreditEntryUnitTests {

    private FixtureConfiguration fixture;

    @Before
    public void setUp() throws Exception {
        fixture = Fixtures.newGivenWhenThenFixture(CreditEntry.class);
    }

    @Test
    public void creditEntryCreated() throws Throwable {
        final Long entryId = 1L;
        final int amount = 100;

        fixture.given().when(new CreateCreditEntryCommand(entryId, amount))
            .expectEvents(new CreditEntryCreatedEvent(entryId, amount));
    }

    @Test
    public void creditEntryMadeEffective() throws Throwable {
        final Long entryId = 1L;
        final int amount = 100;
        final Date start = nov(2011, 12);
        final Date end = nov(2012, 12);// a year effective period

        fixture.given(new CreditEntryCreatedEvent(entryId, amount))
            .when(new MakeCreditEntryEffectiveCommand(entryId, start, end))
            .expectEvents(new CreditEntryMadeEffectiveEvent(entryId, start, end));
    }

    //omitted support methods
}

public class CreditEntry extends AbstractAnnotatedAggregateRoot {

    @AggregateIdentifier
    private Long id;
    private int amount;
    private Date effectiveDateRangeStart;
    private Date effectiveDateRangeEnd;
    private Status status;

    @CommandHandler
    public CreditEntry(CreateCreditEntryCommand command) {
        apply(new CreditEntryCreatedEvent(
            command.getEntryId(), command.getAmount()));
    }

    @EventHandler
    public void on(CreditEntryCreatedEvent event) {
        this.id = event.getEntryId();
        this.amount = event.getAmount();
        this.status = Status.NEW;
    }

    @CommandHandler
    public void markCompleted(MakeCreditEntryEffectiveCommand command) {
        apply(new CreditEntryMadeEffectiveEvent(
            command.getEntryId(), command.getStart(), command.getEnd()));
    }

    @EventHandler
    public void on(CreditEntryMadeEffectiveEvent event) {
        this.effectiveDateRangeStart = event.getStart();
        this.effectiveDateRangeEnd = event.getEnd();
        this.status = Status.EFFECTIVE;
    }

    public CreditEntry() {}

    public enum Status {
        NEW, EFFECTIVE, EXPIRED
    }
}
Run Code Online (Sandbox Code Playgroud)

测试代码驱使我用axon-framework编写域模型和集成代码,但它没有涵盖事件产生的副作用.我在哪里测试它们?例如,当有效时,信用分录的状态应该是有效的.我应该在其他测试方法中创建一个CreditEntry实例,并通过调用特定的(...事件事件)方法进行测试吗?

还有一个问题是:我应该在哪里放置业务验证逻辑?在命令处理程序方法?假设如果CreditEntry已经有效则不能再次生效.

@CommandHandler
public void markCompleted(MakeCreditEntryEffectiveCommand command) {
    if (is(NEW)) {
        apply(new CreditEntryMadeEffectiveEvent(
            command.getEntryId(), command.getStart(), command.getEnd()));
    } else {
        throw new IllegalStateException(.......);
    }
}
Run Code Online (Sandbox Code Playgroud)

任何想法都很感激,谢谢.

spa*_*spa 5

关于你的第一个问题: 你的意思是副作用你的聚合对象的内部状态?Given-When-Then夹具测试将聚合视为一种黑盒子.确实,没有必要测试内部状态.重要的是应用正确的事件.

因此,例如,您甚至可能最终得到没有任何字段的聚合(期望ID),因为您的决策逻辑不依赖于任何内部状态.根据经验,我只保存在聚合对象中的事件中传输的数据,如果我稍后需要它来决定应用哪些事件或者它是否更改了事件中应用的数据.

如果你记住这一点,你就不必测试内部状态.您只需在给定子句中配置具有特定事件的聚合(构建某种状态),然后应用命令.如果正确的事件出来......你已经完成了.

关于第二个问题: 业务验证应该在命令处理程序中.所以在apply调用方法之前应该验证所有内容.其中一个原因是:想象一下验证逻辑在整个生命周期内发生变化的系统,但您必须处理引入系统时输入的旧数据.如果验证将在事件处理程序中并且验证与首次引入事件时的验证不同,则从事件中加载聚合可能会失败,因为"旧"数据与当前验证逻辑不匹配.