Search code examples
javaunit-testingjunitapache-camel

Camel retry - unit test redelivery attempts count using MockEndpoint


I'm trying to write simple unit test verifying if Camel route is configured properly and redelivering attempts were truly made. I have this route builder implementation:

class LocalEndpoint extends RouteBuilder {
    private final CamelContext camelContext;
    private final String targetEndpoint;

    public LocalEndpoint(CamelContext camelContext, String targetEndpoint) {
        super(camelContext);
        this.camelContext = camelContext;
        this.targetEndpoint = targetEndpoint;
    }

    @Override
    public void configure() {
        onException(TargetServerErrorException.class)
            .maximumRedeliveries(2)
            .redeliveryDelay(2500)
            .retryAttemptedLogLevel(LoggingLevel.WARN)
            .process(exchange -> {
                // bellow equals 2
                int redeliveryAttempts = exchange.getIn().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
            })
            .stop();

        from("direct:local-endpoint")
            .marshal()
            .json()
            .to(targetEndpoint)
            .choice()
            .when(header(HTTP_RESPONSE_CODE).isEqualTo("500"))
                .throwException(new TargetServerErrorException())
            .endChoice()
            .end();
    }

    public void callTarget(String objectId) {
        camelContext.createProducerTemplate().sendBody("direct:local-endpoint", Map.of("objectId", objectId));
    }
}

and I've came up with bellow test:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LocalEndpointTest {
    private static final String MOCK_ENDPOINT = "mock:target-endpoint";

    CamelContext camelContext;
    MockEndpoint mockEndpoint;

    LocalEndpoint localEndpoint;

    @BeforeAll
    void beforeAll() throws Exception {
        camelContext = new DefaultCamelContext();
        localEndpoint = new LocalEndpoint(camelContext, MOCK_ENDPOINT);
        camelContext.addRoutes(localEndpoint);
        camelContext.start();

        mockEndpoint = camelContext.getEndpoint(MOCK_ENDPOINT, MockEndpoint.class);
    }

    @AfterAll
    void afterAll() {
        camelContext.stop();
    }

    @Test
    void testCallTarget_serverError() throws InterruptedException {
        // given
        mockEndpoint.returnReplyHeader(Exchange.HTTP_RESPONSE_CODE, new ConstantExpression("500"));
        mockEndpoint.expectedMessageCount(3); // gives: AssertionError: mock://target-endpoint Received message count. Expected: <3> but was: <1>
        mockEndpoint.expectedHeaderReceived(Exchange.REDELIVERY_COUNTER, 2); // gives: AssertionError: mock://target-endpoint No header with name CamelRedeliveryCounter found for message: 0

        // when
        Throwable thrown = catchThrowable(() -> localEndpoint.callTarget("object-id"));

        // then
        mockEndpoint.assertIsSatisfied();
        assertThat(thrown)
            .cause().isInstanceOf(TargetServerErrorException.class)
            .hasMessageContaining("received:", 500);
    }
}

I've expected to see value 3 in message count assertion, but to my surprise it was 1.

I assume this issue and lack of Camel headers in mockEndpoint's exchange may be related to separated onException(Exception.class) block in route definition and different context, but I'm not sure as I don't know Camel well enough.

Is there any way to verify re-delivery attempts in such a simple test? Or at least is it possible to get somehow Camel headers (like CamelRedeliveryCounter) within mockEndpoint?

Camel version: 4.3.0

Edit:

I've found this info: https://camel.apache.org/manual/faq/how-do-i-retry-processing-a-message-from-a-certain-point-back-or-an-entire-route.html

Issue may be caused due to:

By default Apache Camel will perform any redelivery (retry) attempts from the point of failure

I've added .errorHandler(noErrorHandler()) and redefined the route a bit:

onException(TargetServerErrorException.class)
    .maximumRedeliveries(2)
    .redeliveryDelay(2500)
    .retryAttemptedLogLevel(LoggingLevel.WARN)
    .process(exchange -> {
        int redeliveryAttempts = exchange.getIn().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class); // equals 2
    });

from("direct:local-endpoint")
    .marshal()
    .json()
    .to("direct:retryable")
    .end();

from("direct:retryable")
    .errorHandler(noErrorHandler())
    .to(targetEndpoint)
    .choice()
    .when(header(HTTP_RESPONSE_CODE).isEqualTo("500"))
        .throwException(new TargetServerErrorException())
    .endChoice()

but still it doesn't work - I'm getting:

java.lang.AssertionError: mock://target-endpoint Received message count. Expected: <3> but was: <1>

Solution

  • It turned out that the already mentioned solution from FAQ does work fine and is exactly what I was looking for. I also had some garbage in tests and after cleaning them, and adding mockEndpoint.reset() everything started to pass successfully.