Spring Boot Apache Camel Routes testing

你说的曾经没有我的故事 提交于 2019-12-01 14:43:02

In Camel 2.22.0 and ongoing, which supports Spring Boot 2 you can use the following template to test your routes with Spring Boot 2 support:

@RunWith(CamelSpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
    Route1.class,
    Route2.class,
    ...
})
@EnableAutoConfiguration
@DisableJmx
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class RouteTest {

  @TestConfiguration
  static class Config {
    @Bean
    CamelContextConfiguration contextConfiguration() {
      return new CamelContextConfiguration() {
        @Override
        public void beforeApplicationStart(CamelContext camelContext) {
          // configure Camel here
        }

        @Override
        public void afterApplicationStart(CamelContext camelContext) {
          // Start your manual routes here
        }
      };
    }

    @Bean
    RouteBuilder routeBuilder() {
      return new RouteBuilder() {
        @Override
        public void configure() {
          from("direct:someEndpoint").to("mock:done");
        }
      };
    }

    // further beans ...
  }

  @Produce(uri = "direct:start")
  private ProducerTemplate template;
  @EndpointInject(uri = "mock:done")
  private MockEndpoint mockDone;

  @Test
  public void testCamelRoute() throws Exception {
    mockDone.expectedMessageCount(1);

    Map<String, Object> headers = new HashMap<>();
    ...
    template.sendBodyAndHeaders("test", headers);

    mockDone.assertIsSatisfied();
  }
}

Spring Boot distinguishes between @Configuration and @TestConfiguration. The primer one will replace any existing configuration, if annotated on a top-level class, while @TestConfiguration will be run in addition to the other configurations.

Further, in larger projects you might run into auto-configuration issues as you can't rely on Spring Boot 2 to configure your custom database pooling or what not correctly or in cases where you have a specific directory structure and the configurations are not located within a direct ancestor directory. In that case it is proabably preferable to omit the @EnableAutoConfiguration annotation. In order to tell Spring to still auto-configure Camel you can simply pass CamelAutoConfiguration.class to the classes mentioned in @SpringBootTest

@SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
    Route1.class,
    Route2.class,
    RouteTest.Config.class,
    CamelAutoConfiguration.class
}

As no automatic configuration is performed, Spring won't load the test configuration inside your test class nor initialize Camel as well. By adding those configs to the boot classes manually Spring will do it for you.

Did you try using Camel test runner?

@RunWith(CamelSpringJUnit4ClassRunner.class)

If you are using camel-spring-boot dependency, you may know that it uses auto configuration to setup Camel:

CamelAutoConfiguration.java

It means that you may also need to add @EnableAutoConfiguration to your test.

For one route with MQ and Spring Boot like this:

@Component
public class InboundRoute extends RouteBuilder {

  @Override
  public void configure() {
    JaxbDataFormat personDataFormat = new JaxbDataFormat();
    personDataFormat.setContextPath(Person.class.getPackage().getName());
    personDataFormat.setPrettyPrint(true);
    from("direct:start").id("InboundRoute")
        .log("inbound route")
        .marshal(personDataFormat)
        .to("log:com.company.app?showAll=true&multiline=true")
        .convertBodyTo(String.class)
        .inOnly("mq:q.empi.deim.in")
        .transform(constant("DONE"));
  }
}

I use adviceWith in order to replace the endpoint and use only mocks:

@RunWith(CamelSpringBootRunner.class)
@UseAdviceWith
@SpringBootTest(classes = InboundApp.class)
@MockEndpoints("mock:a")
public class InboundRouteCamelTest {

  @EndpointInject(uri = "mock:a")
  private MockEndpoint mock;

  @Produce(uri = "direct:start")
  private ProducerTemplate template;

  @Autowired
  private CamelContext context;

  @Test
  public void whenInboundRouteIsCalled_thenSuccess() throws Exception {
    mock.expectedMinimumMessageCount(1);
    RouteDefinition route = context.getRouteDefinition("InboundRoute");
    route.adviceWith(context, new AdviceWithRouteBuilder() {
      @Override
      public void configure() {
        weaveByToUri("mq:q.empi.deim.in").replace().to("mock:a");
      }
    });
    context.start();

    String response = (String) template.requestBodyAndHeader("direct:start",
        getSampleMessage("/SimplePatient.xml"), Exchange.CONTENT_TYPE, MediaType.APPLICATION_XML);

    assertThat(response).isEqualTo("DONE");
    mock.assertIsSatisfied();
  }

  private String getSampleMessage(String filename) throws Exception {
    return IOUtils
        .toString(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8.name());
  }
}

I use the following dependencies: Spring Boot 2.1.4-RELEASE and Camel 2.23.2. The complete source code is available on Github.

This is how I did this finally

@RunWith(SpringRunner.class)
public class CamelRouteConfigTest extends CamelTestSupport {

    private static final Logger LOG = LoggerFactory.getLogger(CamelRouteConfigTest.class);
    private static BrokerService brokerSvc = new BrokerService();

    @Mock
    private QueueEventHandler queueEventHandler;

    @BeforeClass
    //Sets up a embedded broker.
    public static void setUpBroker() throws Exception {
        brokerSvc.setBrokerName("TestBroker");
        brokerSvc.addConnector("tcp://localhost:61616");
        brokerSvc.setPersistent(false);
        brokerSvc.setUseJmx(false);
        brokerSvc.start();
    }

    @Override
    protected RoutesBuilder createRouteBuilder() throws Exception {
        return new CamelConfig().route();
    }

    // properties in .yml has to be loaded manually. Not sure of .properties file
    @Override
    protected Properties useOverridePropertiesWithPropertiesComponent() {
        YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
        try {
            PropertySource<?> applicationYamlPropertySource = loader.load(
                "properties", new ClassPathResource("application.yml"),null);// null indicated common properties for all profiles.
            Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
            Properties properties = new Properties();
            properties.putAll(source);
            return properties;
        } catch (IOException e) {
            LOG.error("application.yml file cannot be found.");
        }

        return null;
    }

    @Override
    protected JndiRegistry createRegistry() throws Exception {
        JndiRegistry jndi = super.createRegistry();
        MockitoAnnotations.initMocks(this);
        jndi.bind("queueEventHandler", queueEventHandler);

        return jndi;
    }

    @Test
    // Sleeping for a few seconds is necessary, because this line template.sendBody runs in a different thread and
    // CamelTest takes a few seconds to do the routing.
    public void testRoute() throws InterruptedException {
        template.sendBody("activemq:productpushevent", "HelloWorld!");
        Thread.sleep(2000);
        verify(queueEventHandler, times(1)).handleQueueEvent(any());
    }

    @AfterClass
    public static void shutDownBroker() throws Exception {
        brokerSvc.stop();
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!