Chapter 8. Testing Camel Spring Boot applications
8.1. Testing with JUnit 5 Copy linkLink copied to clipboard!
For testing, Maven users will need to add the following dependencies to their pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.5.11</version> <!-- Use the same version as your Spring Boot version -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring-junit5</artifactId>
<version>4.14.4.redhat-00008</version> <!-- use the same version as your Camel core version -->
<scope>test</scope>
</dependency>
To test a Camel Spring Boot application, annotate your test class(es) with @CamelSpringBootTest. This brings Camel’s Spring Test support to your application, so that you can write tests using Spring Boot test conventions.
To get the CamelContext or ProducerTemplate, you can inject them into the class in the normal Spring manner, using @Autowired.
You can also use camel-test-spring-junit5 to configure tests declaratively. This example uses the @MockEndpoints annotation to auto-mock an endpoint:
@CamelSpringBootTest
@SpringBootApplication
@MockEndpoints("direct:end")
public class MyApplicationTest {
@Autowired
private ProducerTemplate template;
@EndpointInject("mock:direct:end")
private MockEndpoint mock;
@Test
public void testReceive() throws Exception {
mock.expectedBodiesReceived("Hello");
template.sendBody("direct:start", "Hello");
mock.assertIsSatisfied();
}
}
For a sample application, see the Infinispan example in the camel-spring-boot-examples repository.
The @CamelSpringBootTest extends @SpringBootTest functionality, including features, plus: - Provides route testing utilities - Includes CamelContext, route advisors, and mock endpoints - Enables Camel test annotations - Works with @MockEndpoints, @UseAdviceWith, etc.
An example of @UseAdviceWith is when a route modification is applied at runtime:
@CamelSpringBootTest
@SpringBootApplication
public class MySpringBootApplicationTest {
@Autowired
private CamelContext camelContext;
@Autowired
private ProducerTemplate producerTemplate;
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplicationTest.class, args);
}
// Spring context fixtures
@Configuration
static class TestConfig {
@Bean
RoutesBuilder route() {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("timer:hello1?period={{timer.period}}").routeId("hello1")
.transform().method("myBean", "saySomething")
.filter(simple("${body} contains 'foo'"))
.to("log:foo")
.end()
.to("stream:out");
}
};
}
}
@Test
public void test() throws Exception {
// Apply advice before getting the mock endpoint
AdviceWith.adviceWith(camelContext, "hello1",
// intercepting an exchange on route
r -> {
// replacing consumer with direct component
r.replaceFromWith("direct:start");
// mocking producer
r.mockEndpoints("stream*");
}
);
// Start the context after applying advice
camelContext.start();
// Get mock endpoint after advice is applied
MockEndpoint mock = camelContext.getEndpoint("mock:stream:out", MockEndpoint.class);
// setting expectations
mock.expectedMessageCount(1);
mock.expectedBodiesReceived("Hello World");
// invoking consumer
producerTemplate.sendBody("direct:start", null);
// asserting mock is satisfied
mock.assertIsSatisfied();
}
}
In some case we need to declare both, a combination of @CamelSpringBootTest and @SpringBootTest, when an applicationContext is strictly required, just adding @SpringBootTest(classes = DocTest.TestApplication.class) to specify which configuration class to use:
@CamelSpringBootTest
@SpringBootTest(classes = MySpringBootApplicationTest.TestConfig.class)
@SpringBootApplication
@MockEndpoints("direct:end")
public class MySpringBootApplicationTest {
@Autowired
private ProducerTemplate template;
@EndpointInject("mock:direct:end")
private MockEndpoint mock;
@Configuration
static class TestConfig {
@Bean
RoutesBuilder route() {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").routeId("hello2")
.setBody(simple("Hello World"))
.log("Received: ${body}")
.to("direct:end");
from("direct:end")
.log("${body}");
}
};
}
}
@Test
public void testReceive() throws Exception {
mock.expectedMessageCount(1);
mock.expectedBodiesReceived("Hello World");
template.sendBody("direct:start", null);
mock.assertIsSatisfied();
}
}
Another approach is to test classic Spring XML context setup. In this case, we need to use @ContextConfiguration to specify the XML file, retrieving the Main Camel runtime context through dependency injection:
@Autowired
protected CamelContext camelContext;
Then, to reloads the Spring ApplicationContext after each test method we can use:
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
Alternative modes: BEFORE_CLASS, AFTER_CLASS, BEFORE_EACH_TEST_METHOD
A full example is described here:
package com.redhat.plain;
.....
@CamelSpringBootTest
@ContextConfiguration
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class MySpringBootApplicationPlainTest {
@Autowired
protected CamelContext camelContext;
@EndpointInject("mock:a")
protected MockEndpoint mockA;
@Produce("direct:start")
protected ProducerTemplate start;
@Test
public void testPositive() throws Exception {
assertEquals(ServiceStatus.Started, camelContext.getStatus());
start.sendBody("Hello");
MockEndpoint.assertIsSatisfied(camelContext);
mockA.expectedBodiesReceived("Hello");
}
}
Where Spring XML context requires to be placed under resources at declared class package com.redhat.plain using the naming pattern className-context.xml
Project Structure
src/
├── main/
│ ├── java/
│ │ └── com/redhat/
│ │ ├── Application.java
│ │ └── route/
│ │ └── MyRoute.java
│ └── resources/
│ └── application.yml
└── test/
├── java/
│ └── com/redhat/
└── resources/
└── MySpringBootApplicationPlainTest-context.yml
XML declaration for the example above:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
">
<!-- Camel Context configuration -->
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<route xmlns="http://camel.apache.org/schema/spring" id="helloX">
<from id="from1" uri="direct:start"/>
<filter id="filter1">
<simple>${body} contains 'foo'</simple>
<to id="to1" uri="log:foo"/>
</filter>
<to id="to2" uri="stream:out"/>
</route>
</camelContext>
</beans>