Reputation: 4795
During the migration from Camel 2 to 3, my error routing tests broke.
The pattern that I follow is to force an exception and assert that the onException()
block sends to my metrics route with the appropriate tags.
I'm using uri pattern matching to individually test that each tag is emitted...this strongly influences the test pattern
Note: In both examples below the createRouteBuilder()
method is identical
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel2Test : CamelTestSupport() {
val startUri = "direct:start"
val baseMetricsUri = "micrometer:counter:errors"
// Want to use pattern to test each individual tag here
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(fullMetricsUri)
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `metric with tag B is emitted`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
context.getRouteDefinition(startUri)
.adviceWith(context, object : RouteBuilder() {
override fun configure() {
interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
})
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.AdviceWithRouteBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel3Test : CamelTestSupport() {
val startUri = "direct:start"
val baseMetricsUri = "micrometer:counter:errors"
// Want to use pattern to test each individual tag here
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(fullMetricsUri)
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `metric with tag B is emitted`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
The mockEndpoint
isn't receiving the exchange and it is instead still going to the metrics endpoint.
In Camel 3 how can I intercept a route like I was in Camel 2 using patterns? Manual testing shows that the error routing is behaving as expected in prod, so this seems to be a test configuration issue.
mock:
directly in the route.When I don't need pattern matching then this alternate approach works
override fun isMockEndpointsAndSkip() = myUri
// ... in test
getMockEndpoint("mock:$myUri").expectedMessageCount(1)
Upvotes: 4
Views: 4691
Reputation: 855
Some additional info for great @ShellDragon answer. During debugging your examples i found interesting thing. Your examples don't work in camel 3 cause SendProcessor lost some part of code (doStart method):
// the destination could since have been intercepted by a interceptSendToEndpoint so we got to
// lookup this before we can use the destination
Endpoint lookup = camelContext.hasEndpoint(destination.getEndpointKey());
if (lookup instanceof InterceptSendToEndpoint) {
if (log.isDebugEnabled()) {
log.debug("Intercepted sending to {} -> {}",
URISupport.sanitizeUri(destination.getEndpointUri()), URISupport.sanitizeUri(lookup.getEndpointUri()));
}
destination = lookup;
}
Destination "direct:errors" in 2.x was rewritten by founded intercept-endpoint. But now this code was marked as "old cruft" and removed by @clausibsen . I doubt that this is a bug, because simple interceptSendToEndpoint is still working. Maybe there is changes in using advicewith + interceptors.
Upvotes: 2
Reputation: 4795
This seems to be directly related to the use of onException()
. Apparently in Camel 3 you can no longer intercept directly from onException
, so moving the business logic out of the exception block into a new route allows the intercept to work.
In my case this just required saving relevant onException
information in exchange properties, which could then be referenced when emitting metrics.
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.AdviceWithRouteBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel3ErrorInterceptWorking : CamelTestSupport() {
val startUri = "direct:start"
val errorUri = "direct:errors"
val baseMetricsUri = "micrometer:counter:errors"
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(errorUri)
from(errorUri)
.to(fullMetricsUri) // Moved metrics here from `onException`
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `exception is routed to error logging route`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
Upvotes: 0
Reputation: 1722
First of all, thank you very much for a very well framed question with proper code samples! Mock component's manual refers to introduction of a feature 'Mocking Existing Endpoints' very likely this is what is blocked you. I am not very sure which version of Camel introduced this feature.
In any case, to get around the current limitation, you may use the automatic mocking feature itself. Your test method could be changed as below to get it working.
@Test
fun `exception is routed to error logging route`() {
val exchange = createExchangeWithBody("")
// Create new mock endpoint that will replace our error route
val mockEndpoint = getMockEndpoint("mock:$errorUri")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.mockEndpoints(errorUri)
routeBuilder.interceptSendToEndpoint(errorUri)
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied()
}
There were two changes made to the original code.
mock:test
to be in line with the automatically generated mock endpoint types (mock:direct:errors
)routeBuilder.mockEndpoints(errorUri)
so that camel can automatically inject Mocks, for the patters as described by errorUri
In addition to it, it is possible replace the block below
routeBuilder.mockEndpoints(errorUri)
routeBuilder.interceptSendToEndpoint(errorUri)
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
with a one liner routeBuilder.mockEndpointsAndSkip(errorUri)
, unless there are specific reasons to use intercept
as you've mentioned in your question.
Running your code, without changes clearly show a RouteReifier
hooking in the Mock endpoint, mock://test
in place of direct:errors
. In addition, the context
appeared to have a proper endpointStrategy
as well.
This could be a bug. Though there are easy alternatives, please consider raising this as an issue on ASF Jira as well.
14:32:34.307 [main] INFO org.apache.camel.reifier.RouteReifier - Adviced route before/after as XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="direct:start">
<from uri="direct:start"/>
<onException>
<exception>java.lang.Exception</exception>
<to uri="direct:errors"/>
</onException>
<throwException/>
</route>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="direct:start">
<from uri="direct:start"/>
<onException>
<exception>java.lang.Exception</exception>
<to uri="direct:errors"/>
</onException>
<interceptSendToEndpoint skipSendToOriginalEndpoint="true" uri="direct:errors">
<to uri="mock://test"/>
</interceptSendToEndpoint>
<throwException/>
</route>
import org.apache.camel.Exchange;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Assert;
import org.junit.Test;
public class Camel3RouteTest extends CamelTestSupport {
private static final String startUri = "direct:start";
private static final String errorUri = "direct:errors";
private static final String mockErrorURI = "mock:"+ errorUri;
private static final String ERROR_MESSAGE = "ERROR MESSAGE!";
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
onException(Exception.class)
.to(errorUri);
from(errorUri)
.routeId(errorUri)
.log("error happened!");
from(startUri)
.routeId(startUri)
.throwException(new Exception(ERROR_MESSAGE));
}
};
}
@Test
public void testExecution() throws Exception {
AdviceWithRouteBuilder.adviceWith(context, startUri, adviceWithRouteBuilder -> {
//a.mockEndpointsAndSkip(errorUri);
adviceWithRouteBuilder.mockEndpoints(errorUri);
adviceWithRouteBuilder.interceptSendToEndpoint(errorUri).skipSendToOriginalEndpoint().to(mockErrorURI);
});
MockEndpoint mockEndpoint = getMockEndpoint(mockErrorURI);
mockEndpoint.setExpectedMessageCount(1);
context.start();
sendBody(startUri, "A Test message");
assertMockEndpointsSatisfied();
Assert.assertNotNull(mockEndpoint.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT));
Exception receivedException = (Exception) mockEndpoint.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT);
Assert.assertTrue(receivedException instanceof Exception);
Assert.assertEquals(receivedException.getMessage(), ERROR_MESSAGE);
}
}
Upvotes: 6