Testing saga produced events rather than commands

I’m starting to play with Axon and wanted to test a Saga, but there is something I cannot understand, and I’m maybe doing it wrong.

What I want to do is test that a Saga executes as expected (when triggered by an event). If so, a command is fired, which should produce another event.
I can see that I’m able to test fired commands (with the expectDispatchedCommands() method in the SagaTestFixture class), but I’d rather test the produced events. My train of though is that a command is an implementation detail (a mean to an end), and the event is the truth (the actual system’s state, regardless of how it was produced).

I’m currently unable to do that because the commands are handled in an aggregate that is not part of my test, and I didn’t find out how to register it.

So what are your thoughts about that ? Am I wrong that I should rather test for events than commands ? And if I’m right, how to make the test work ?

Hi Alexandre,

Please see the code example at the end of the Component Command Testing section for some example usages of the .expectEventsMatching() method.

https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/testing#command-component-testing

public class MyCommandComponentTest {
   private FixtureConfiguration<MyAggregate> fixture;

   @Before
   public void setUp() {
      fixture = new AggregateTestFixture<>(MyAggregate.class);
   }

   @Test
    public void testFirstFixture() {
        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()
                     )
            ));
}

Hello Somers,

Thanks for the provided code.
What I want is to test my saga, not the aggregate itself (I already have tests for it). By testing the saga, I want to test a business process rather than a discrete behavior.

Here’s some code (in kotlin) to explain what I have in mind.

//Aggregate
@Aggregate
internal class User() {

    @AggregateIdentifier
    private lateinit var email: Email

    @CommandHandler
    @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
    fun handle(command: RequestUserRegistration): Status<Unit> = Status.of {
        AggregateLifecycle.apply(
            UserRegistrationRequested(
                ...
            )
        )
    }

    @CommandHandler
    fun handle(command: AcceptUserRegistration): Status<Unit> = Status.of {
        AggregateLifecycle.apply(
            UserRegistrationAccepted(
                ...
            )
        )
    }
...
}


// Saga
@Saga
class UserProcess() {

    @Autowired
    @Transient
    private lateinit var commandGateway: CommandGateway

    @Autowired
    @Transient
    private lateinit var deadlineManager: DeadlineManager

    @StartSaga
    @SagaEventHandler(associationProperty = "email")
    fun on(event: UserRegistrationRequested) {
        if (isEmailAccepted(event.email)) {
            commandGateway.send<Status<Unit>>(
                AcceptUserRegistration(event.email, event.timestamp)
            )
        } else {
            deadlineManager.schedule(Duration.ofDays(2), event.email.email)
        }
    }

    @EndSaga
    @SagaEventHandler(associationProperty = "email")
    fun on(event: UserRegistrationAccepted) {}

    @DeadlineHandler
    fun denyUserRegistrationForTimeout(email: String) {
        apply(UserRegistrationDenied(Email(email)))
    }

    @EndSaga
    @SagaEventHandler(associationProperty = "email")
    fun on(event: UserRegistrationDenied) {}
}

At the moment, I have a test that verifies that the saga is started on the UserRegistrationRequested event, and that a command is issued, and another test that verifies that the saga ends when the UserRegistrationAccepted event occurs.
I’d like to have only 1 test that tests that given the right conditions, the user registration request is accepted automatically, without having the bother myself with implementation details (the fact that I have 2 handlers for that, the exact issued command, etc.).

Is that clearer ? Thanks again for your answer.

I’d like to welcome both you, @Alexandre_FILLATRE, and @somersbmatthews to the forum with these posts. I hope your journeys with Axon Framework have been pleasant so far.

Concerning your request, @Alexandre_FILLATRE, the Saga is intended to:

  1. Handle an event to
  2. trigger an activity (e.g., dispatching a command)

The Saga implementation itself should not be concerned with command handling other than the result of command handling.
When I am talking about the result, I mean whether the command was handled successfully, yes/no.
Any events that might’ve been published due to command handling are not part of the response flowing back to the spot where you’ve dispatched the command.

And, again, the Saga should not be concerned about this.
For all it knows, the command is sent to an application by a team within your company you have never had face-to-face contact with.

If you do have a requirement to see the flow from the Saga to the Aggregate, I’d recommend you write an Integration Test.
The Test Fixtures provided by Axon Framework are scoped around the intent of the component.

So for Aggregates, that’s handling a command and publishing events.
And for a Saga, that’s handling events and triggering activities (like commands).

I hope this clarifies the intent around the SagaTestFixture for you, @Alexandre_FILLATRE!
Feel free to post follow-up questions with concerns about this.

Example of this in kotlin: Integration testing with event sourcing systems - Stack Overflow

Hello @Steven_van_Beelen, thanks a lot for the detailed explanation !

I now understand that my use-case is a peculiar case, when the Saga listen for an event produced by its own commands. There are many cases where such an event would be generated by another system’s part, hence difficult to test in a whole process.

1 Like