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)
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)
}
}