Summary:
I am getting an IllegalArgumentException from my junit trying to test a templated route that is built from a template that I created in a 2nd RouteBuilder class.
You can see in the CreateTemplate class I'm trying to use the 3rd template parameter to set the .id() string for the TO file producer. This is done so that I can find (via weaveById) and mock this producer in the junit. However, it seems that the id is not setup correctly, and the weaveById fails.
If I create the route created directly, I mean, not via the template, this works just fine, and I am able to mock the producer with the weaveById() method.
Using spring-boot 2.7.0, Camel 3.16.0, and openjdk-11.
What am I missing here? Thanks for your help.
Note: I did find a workaround which is to use weaveByType(). This is commented out in the junit but does work fine. But would still like to understand why the weaveById() is not working here.
Problem details and code:
The junit test class:
package com.jvh.routes;
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.AdviceWith;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
import org.apache.camel.test.spring.junit5.UseAdviceWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
@SpringBootTest
@CamelSpringBootTest
@UseAdviceWith
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class RouteTest {
@Autowired
private CamelContext camelContext;
@Autowired
private ProducerTemplate producerTemplate;
@EndpointInject("mock:endpointMock")
private MockEndpoint mockEndpoint;
@BeforeEach
public void setup() throws Exception {
mockEndpoint.reset();
AdviceWith.adviceWith(camelContext, "my-test-file-route", routeBuilder -> {
routeBuilder.replaceFromWith("direct:start");
routeBuilder.weaveById("my-internal-route-id").replace().to(mockEndpoint); // fails
//routeBuilder.weaveByType(ToDefinition.class).replace().to(mockEndpoint); // works
routeBuilder.setLogRouteAsXml(false);
});
camelContext.start();
}
@AfterEach
public void tearDown() {
camelContext.stop();
}
@Test
void test_processMessageToRestApi_Ok() throws Exception {
final String testMsg = "{ test msg }";
// Expect output file endpoint mock to receive 1 message
mockEndpoint.expectedMessageCount(1);
mockEndpoint.expectedBodiesReceived(testMsg);
// Message body
producerTemplate.sendBody("direct:start", testMsg);
mockEndpoint.assertIsSatisfied();
}
}
The template creation class:
package com.jvh.routes;
import org.apache.camel.LoggingLevel;
import org.apache.camel.Ordered;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class CreateTemplate extends RouteBuilder {
@Override
public int getOrder() {
return Ordered.HIGHEST; // required to ensure the template is created first
}
@Override
public void configure() {
routeTemplate("myTemplateId")
.templateParameter("input-directory-param-id")
.templateParameter("output-directory-param-id")
.templateParameter("route-id-param-id")
.from("file:{{input-directory-param-id}}")
.log(LoggingLevel.INFO, log, "--> got file ${header.CamelFileName}")
.to("file:{{output-directory-param-id}}")
.id("{{route-id-param-id}}") // set id here to be able to mock this producer
.log(LoggingLevel.INFO, log, "--> done");
}
}
The templated route creation class:
package com.jvh.routes;
import org.apache.camel.CamelContext;
import org.apache.camel.Ordered;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.TemplatedRouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CreateRoute extends RouteBuilder {
@Autowired
private CamelContext camelContext;
@Override
public int getOrder() {
return Ordered.LOWEST; // route created after the template
}
@Override
public void configure() throws Exception {
final String routeId = TemplatedRouteBuilder.builder(camelContext, "myTemplateId")
.routeId("my-test-file-route")
.parameter("input-directory-param-id", "input-directory")
.parameter("output-directory-param-id", "output-directory")
.parameter("route-id-param-id", "my-internal-route-id") // parameter to set the producer id
.add();
log.info("Route {} created from template", routeId);
}
}
The exception thrown when I run the junit test:
java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]]
at org.apache.camel.builder.AdviceWithTasks$1.task(AdviceWithTasks.java:226)
at org.apache.camel.builder.AdviceWith.doAdviceWith(AdviceWith.java:222)
at org.apache.camel.builder.AdviceWith.adviceWith(AdviceWith.java:75)
at com.jvh.routes.RouteTest.setup(RouteTest.java:38)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
<snip...>
Some relavent debug logs from the junit run:
<snip...>
2022-05-23 16:36:58.535 DEBUG 10916 --- [ main] o.a.camel.spring.SpringCamelContext : onApplicationEvent: org.springframework.test.context.event.AfterTestMethodEvent[source=[DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]]
2022-05-23 16:36:58.535 DEBUG 10916 --- [ main] tractDirtiesContextTestExecutionListener : After test method: context [DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]], class annotated with @DirtiesContext [true] with mode [AFTER_EACH_TEST_METHOD], method annotated with @DirtiesContext [false] with mode [null].
2022-05-23 16:36:58.535 DEBUG 10916 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework.web.context.support.GenericWebApplicationContext@15515c51, started on Mon May 23 16:36:49 EDT 2022
2022-05-23 16:36:58.535 DEBUG 10916 --- [ main] o.a.camel.spring.SpringCamelContext : onApplicationEvent: org.springframework.context.event.ContextClosedEvent[source=org.springframework.web.context.support.GenericWebApplicationContext@15515c51, started on Mon May 23 16:36:49 EDT 2022]
2022-05-23 16:36:58.543 DEBUG 10916 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147483647
2022-05-23 16:36:58.543 DEBUG 10916 --- [ main] o.a.c.main.SimpleMainShutdownStrategy : Shutdown called
2022-05-23 16:36:58.545 DEBUG 10916 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2022-05-23 16:36:58.545 DEBUG 10916 --- [ main] o.s.t.c.w.ServletTestExecutionListener : Resetting RequestContextHolder for test context [DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false, 'org.springframework.test.context.support.DependencyInjectionTestExecutionListener.reinjectDependencies' -> true]].
<snip...>
WeaveByID is weaving nodes (ie endpoints) within the context of a specific route.
Given the route:
You can do:
In your example, you are weaving "my-internal-route-id" which is a route, not an endpoint