Uploaded image for project: 'Camel'
  1. Camel
  2. CAMEL-16567

Mocking of consul producers fails

    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: Minor
    • Resolution: Fixed
    • Affects Version/s: 3.9.0
    • Fix Version/s: 3.10.0
    • Component/s: camel-consul, tests
    • Labels:
      None
    • Estimated Complexity:
      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:

      1. 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
      2. 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:

      1. Let InterceptSendToEndpointProcessor set the camelContext on the producer when it receives the camelContext.
      2. 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

            Activity

              People

              • Assignee:
                Unassigned
                Reporter:
                dschulten Dietrich Schulten
              • Votes:
                0 Vote for this issue
                Watchers:
                1 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: