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:
- Handle an event to
- 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.
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