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>
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.