Conceptual: unittests by pass the validation on commands, should events be validated as well?

Assume there is an aggregate that has a state; INIT → OPEN → CLOSED → ARCHIVED. The ChangeState command changes this, and the handle(ChangeState) method validates if the command is allowed to execute, by validating current state vs requested state. For example the transition from INIT directly to CLOSED throws a IllegalStateException, similar to the handle(RedeemCmd) method in the Axon getting started example (Quick Start - Axon Reference Guide)

A test that verifies if the the archived state can be reached would look like this:

	@Test
	void archivedTest() {
		String uuid = ...;
		fixture.given(AggregateCreated.of(uuid))
		  .andGiven(StateChanged.of(uuid, OPEN))
		  .andGiven(StateChanged.of(uuid, CLOSED))
		  .when(ChangeState.of(uuid, ARCHIVED))
		  .expectEvents(StateChanged.of(uuid, ARCHIVED));
	}

But because the unit test sets up the state using events, the validation of the commands is bypassed. That means the OPEN state can be skipped. This test would run as well:

	@Test
	void archivedTest() {
		String uuid = ...;
		fixture.given(AggregateCreated.of(uuid))
		  // skipped .andGiven(StateChanged.of(uuid, OPEN))
		  .andGiven(StateChanged.of(uuid, CLOSED))
		  .when(ChangeState.of(uuid, ARCHIVED))
		  .expectEvents(StateChanged.of(uuid, ARCHIVED));
	}

This unit test is green for an invalid process. This somehow feels wrong, even if it is technically correct. You can probably cover this with other tests making sure the total state transition path is tested. But should an event be validated as well? Can other listeners to events throw exceptions before it is committed to the event store?

Hello Tom,

The goal of the unit test is to validate the execution and outcome of a certain command. In this case, that’s your ChangeState command. Depending on the state, your test will succeed or fail, and the state is defined by the events in your unit test.
The fact that a StateChanged.of(uuid, CLOSED) is in your event store, means that an earlier command has succeeded. We don’t need to test that here, as it is already tested (presumably) by another set of unit tests. So we can assume that part of the process is valid.

If you still want to test the entire process, that’s possible! You can provide earlier state by the .andGivenCommands method on the test fixture. These commands will run normally, and will thus be validated like actual commands. In your case this would look like this:

	@Test
	void archivedTest() {
		String uuid = ...;
		fixture.given(AggregateCreated.of(uuid))
		  .andGivenCommand(ChangeState.of(uuid, OPEN))
		  .andGivenCommand(ChangeState.of(uuid, CLOSED))
		  .when(ChangeState.of(uuid, ARCHIVED))
		  .expectEvents(StateChanged.of(uuid, ARCHIVED));
	}

Technically you could validate the events in your Aggregate, but I would definitely not recommend it. Command handlers should validate and determine the facts. Applying the event implicates that it has happened. Rejecting that event feels like a design flaw.
In addition, by default Axon Framework only rolls the transaction back on a RuntimeException. So if your event handler throws a checked exception, the event would still be applied, but the next time the aggregate would fail to load (since the event is in the store).

I hope this gives you the information you need to make the right decision for your case!

1 Like