Details
-
Bug
-
Status: Resolved
-
Minor
-
Resolution: Fixed
-
3.9.0
-
None
-
Unknown
Description
It is not possible to mock consul producers, at least I have tried with the catalog and agent producers. Test:
public class MockAgentTest extends CamelTestSupport { @Test public void testMockAgent() throws Exception { MockEndpoint mockConsulAgent = getMockEndpoint("mock:consul:agent"); AdviceWith.adviceWith(context, "servicesRoute", a -> { a.mockEndpointsAndSkip("consul:agent*"); }); mockConsulAgent.returnReplyBody(constant(ImmutableMap.of("foo-1", ImmutableService.builder() .id("foo-1") .service("foo") .address("localhost") .port(80) .build()))); @SuppressWarnings("unchecked") Map<String, Service> result = fluentTemplate.to("direct:start").request(Map.class); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start").to("consul:agent?action=" + ConsulAgentActions.SERVICES); } }; } }
Error:
org.apache.camel.FailedToStartRouteException: Failed to start route servicesRoute because of null
at org.apache.camel.impl.engine.RouteService.warmUp(RouteService.java:123)
...
Caused by: java.lang.NullPointerException at org.apache.camel.support.HeaderSelectorProducer.doBuild(HeaderSelectorProducer.java:150) at org.apache.camel.support.service.BaseService.build(BaseService.java:63) at org.apache.camel.support.service.ServiceHelper.buildService(ServiceHelper.java:55) at org.apache.camel.support.service.ServiceHelper.buildService(ServiceHelper.java:72) at org.apache.camel.processor.InterceptSendToEndpointProcessor.doBuild(InterceptSendToEndpointProcessor.java:151) at org.apache.camel.support.service.BaseService.build(BaseService.java:63) at org.apache.camel.support.service.BaseService.init(BaseService.java:79) at org.apache.camel.support.service.BaseService.start(BaseService.java:111) at org.apache.camel.support.service.ServiceHelper.startService(ServiceHelper.java:113) at org.apache.camel.impl.engine.AbstractCamelContext.internalAddService(AbstractCamelContext.java:1465) at org.apache.camel.impl.engine.AbstractCamelContext.addService(AbstractCamelContext.java:1383) at org.apache.camel.processor.SendProcessor.doStart(SendProcessor.java:247) at org.apache.camel.support.service.BaseService.start(BaseService.java:119) at org.apache.camel.support.service.ServiceHelper.startService(ServiceHelper.java:113) at org.apache.camel.support.service.ServiceHelper.startService(ServiceHelper.java:130) at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler.doStart(RedeliveryErrorHandler.java:1638) at org.apache.camel.support.ChildServiceSupport.start(ChildServiceSupport.java:60) ... 92 more
The reason is that camelContext is null in HeaderSelectorProducer, but I am still unsure why that is:
@Override protected void doBuild() throws Exception { super.doBuild(); String key = this.getClass().getName(); String fqn = RESOURCE_PATH + key; // -------- camelContext is null here: strategy = camelContext.adapt(ExtendedCamelContext.class).getBootstrapFactoryFinder(RESOURCE_PATH) .newInstance(key, InvokeOnHeaderStrategy.class) .orElseThrow(() -> new IllegalArgumentException("Cannot find " + fqn + " in classpath.")); ...
Same with catalog:
public class MockCatalogTest extends CamelTestSupport { @Test public void testMockCatalog() throws Exception { MockEndpoint mockConsulAgent = getMockEndpoint("mock:consul:catalog"); AdviceWith.adviceWith(context, "servicesRoute", a -> { a.mockEndpointsAndSkip("consul:catalog*"); }); mockConsulAgent.returnReplyBody(constant(singletonList(ImmutableNode.builder().node("node-1").build()))); @SuppressWarnings("unchecked") Map<String, Service> result = fluentTemplate.to("direct:start").request(Map.class); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start").to("consul:catalog?action=" + ConsulCatalogActions.LIST_NODES); } }; } }
Why this fails is not clear to me. Two instances of ConsulAgentProducer are being created. At runtime the method setCamelContext of the failing instance never gets called, although it is a CamelContextAware. I suspect that it is the mock or the real object behind the mock which is not a CamelContextAware so that it receives no camelContext. My assumption is that a mock is a kind of wrapper around the real thing, but I wasn't able to understand the relationship between the two enough to fix this. The instances look very similar, the failing instance appears not to be a Java proxy.
Anyone who understands the mocking mechanics better than me: what might go wrong here? Any hints how it should work? Is the mock a proxy or what exactly is the relationship between mock and mocked?
UPDATE
The failing instance is the delegate ConsulAgentProducer inside an InterceptSendToEndpointProcessor. The latter is not a CamelContextAware, hence it receives no camelContext. I see two different approaches:
- The delegate inside the InterceptSendToEndpointProcessor could be made to receive a camelContext, e.g. by making the InterceptSendToEndpointProcess a CamelContextAware and setting the camelContext on the delegate
- The method invocation HeaderSelectorProducer.doBuild() could be skipped somehow
Advice would be very much appreciated.
UPDATE 2
Turns out, it is not the delegate (ConsulEndpoint) member nor the endpoint (DefaultInterceptorSendToEndpoint) member inside InterceptSendToEndpointProcessor which has no CamelContext, but the producer (ConsulAgentProducer) member, which holds a second instance of the producer. Again, two approaches:
- Let InterceptSendToEndpointProcessor set the camelContext on the producer when it receives the camelContext.
- Find out why the second producer inside InterceptSendToEndpointProcessor does not receive a camelContext when it gets constructed
UPDATE 3
DefaultInterceptSendToEndpoint is responsible to create the producer in createAsyncProducer. One could call camelContext.addService(producer) there, in order to initialize the delegate producer like any other component. It does fix the problem, but I am unsure if the delegate producer should be added as a service or if it is the whole point not to add it.
DefaultInterceptSendToEndpoint.java
@Override public AsyncProducer createAsyncProducer() throws Exception { AsyncProducer producer = delegate.createAsyncProducer(); camelContext.addService(producer); // <-- insert this to initialize the producer with camelContext return camelContext.adapt(ExtendedCamelContext.class).getInternalProcessorFactory() .createInterceptSendToEndpointProcessor(this, delegate, producer, skip); }
The alternative would still be to make InterceptSendToEndpointProcessor a CamelContextAware and if its producer is a CamelContextAware in turn, set the camelContext on the producer, hold it in the InterceptSendToEndpointProcessor and get it from there in getCamelContext. Somehow this feels less intrusive than adding the producer as a service.
What would you recommend?
Attachments
Issue Links
- links to