Testing exceptions with SagaTestFixture

Hello

I was wondering how I could test exceptions in a saga via the SagaTestFixture. While the AggregateTestFixture has the “expectException()” I can’t find anything similar for the SagaTestFixture.

In some use cases, we would we get an exception in a saga event handler on which we would like to retry the handling of the event and I would like to test this behaviour.

Hi @Laura_Winnen, let me try to help you on this.

For the SagaTestFixture you can simply “mock” the command gateway using the SagaTestFixture#setCallbackBehavior(CallbackBehavior) method.

To give a simple example, you can make something like this:

fixture.setCallbackBehavior((commandPayload, commandMetaData) -> {
	if (commandPayload instanceof MyCommand) {
		throw new IllegalStateException("some exception when handling MyCommand");
	}
	return null;
});

Hope it helps.

Hi @lfgcampos

That part I know.

I’m wondering how to test the behaviour once the exception is thrown. I can’t check it with @Test(expected= IllegalStateException.class) nor with a fixture.whenPublishingA(myEvent).expectException(IllegalStateException);
The test fixture just logs the failure:

2020-12-21 08:42:44,141 ERROR [org.axonframework.eventhandling.LoggingErrorHandler] [main] EventListener [AnnotatedSaga] failed to handle event [d66df578-2c9a-4ad0-8690-201bee0b49cd] (com.brutex.bvs.ams.events.project.ProjectCreatedEvent). Continuing processing with next listener 
java.lang.IllegalStateException: some exception when handling MyCommand

Hi @lfgcampos,

Any update on this?

I just stumbled upon this myself, the SagaTestFixture is logging the exception instead of throwing it.

2021-01-25 18:56:19,229 ERROR [org.axonframework.eventhandling.LoggingErrorHandler] [main] EventListener [AnnotatedSaga] failed to handle event [a9222546-8fc7-4b41-b299-76ea137d58d4] (events.material.MaterialVersionUnpublishedEvent). Continuing processing with next listener 

java.lang.NullPointerException: null

The exception is not considered as a result of a @SagaEventHandler annotated method. This is different for command handling, as there is a Command Response. The response can be successful (a void handler) or exceptional (from the exception thrown in the command handler.

@Laura_Winnen, the retry behaviour you are speaking of is likely maintained inside the @SagaEventHandler annotated method, correct? That most mean you are invoking the CommandGateway in a try-catch block. Thus, it is your implementation which already catches the exception. Henceforth, there wouldn’t even be anything to expect in such a scenario.

The solution given by @lfgcampos signals the approach of how to define what the CommandBus will do when you dispatch a command. That means that it allows you to enter your catch-block, thus validate the type of (message) operation you’d perform in the event handler.

If my assumptions are incorrect, and you truly have a situation where the exception does need to bubble up out of the Saga, then you’d have to take a look at the ListenerInvocationErrorHandler. Exceptions are thrown from within event handlers (like a @SagaEventHandler annotated method) are caught in a so-called EventHandlerInvoker. This invoker will call the configured ListenerInvocationErrorHandler for every exception being caught by the invoker.

As it stands, you can configure the ListenerInvocationErrorHandler for the SagaTestFixture if you want. It defaults to the LoggingErrorHandler both you @Laura_Winnen and you @dmarcelino have noted. If you want the exception to propagate further, you can change it to a PropagatingErrorHandler. Or, you can define a custom ListenerInvocationErrorHandler, which can validate the exception for your if you will.

Lastly, if you feel there’s benefit in an expectException method on the SagaTestFixture, it would be great if either of you could create an issue for it here.

Hi Steven

How do I configure the ListenerInvocationErrorHandler for the SagaTestFixture ?

The SagaTestFixture as the registerListenerInvocationErrorHandler method.
Hence, you would, when constructing the SagaTestFixture, invoke the registerListenerInvocationErrorHandler providing the ListenerInvocationErrorHandler you’d want to use in your tests.