Testing
Testing is a crucial activity in any piece of software development or integration. Typically Camel Riders use various different technologies wired together in a variety of patterns with different expression languages together with different forms of Bean Integration and Dependency Injection so its very easy for things to go wrong! . Testing is the crucial weapon to ensure that things work as you would expect .
Camel is a Java library so you can easily wire up tests in whatever unit testing framework you use (JUnit 3.x (deprecated) or 4.x). However the Camel project has tried to make the testing of Camel as easy and powerful as possible so we have introduced the following features.
Testing mechanisms
The following mechanisms are supported
Name | Component | Description |
---|---|---|
Camel Test |
|
Is a standalone Java library letting you easily create Camel test cases using a single Java class for all your configuration and routing without using CDI or Spring for Dependency Injection which does not require an in-depth knowledge of CDI or Spring + Spring Test. Supports JUnit 3.x (deprecated) and JUnit 4.x based tests. |
CDI Testing |
|
Provides a JUnit 4
runner that bootstraps a test environment using CDI so that you don’t have
to be familiar with any CDI testing frameworks and can concentrate on the
testing logic of your Camel CDI applications. |
Spring Testing |
|
Supports
JUnit 3.x (deprecated) or JUnit 4.x based tests that bootstrap a test
environment using Spring without needing to be familiar with Spring
Test. The plain JUnit 3.x/4.x based tests work very similar to the
test support classes in |
Blueprint Testing |
|
Camel 2.10: Provides the ability to do unit testing on blueprint configurations |
In all approaches the test classes look pretty much the same in that they all reuse the Camel binding and injection annotations.
Camel Test Example
Here is the Camel Test example:
public class FilterTest extends ContextTestSupport {
@Test
public void testSendMatchingMessage() throws Exception {
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(1);
resultEndpoint.message(0).exchangeProperty(Exchange.FILTER_MATCHED).isEqualTo(true);
template.sendBodyAndHeader("direct:start", "<matched/>", "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
@Test
public void testSendNotMatchingMessage() throws Exception {
MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("direct:start", "<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
}
};
}
}
Notice how it derives from the Camel helper class CamelTestSupport
but
has no CDI or Spring dependency injection configuration but instead
overrides the createRouteBuilder()
method.
CDI Test Example
Here is the CDI Testing example:
@RunWith(CamelCdiRunner.class)
public class FilterTest {
@EndpointInject("mock:result")
protected MockEndpoint resultEndpoint;
@Produce("direct:start")
protected ProducerTemplate template;
@Before
public void before() {
resultEndpoint.reset();
}
@Test
public void testSendMatchingMessage() throws Exception {
String expectedBody = "<matched/>";
resultEndpoint.expectedBodiesReceived(expectedBody);
template.sendBodyAndHeader(expectedBody, "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
@Test
public void testSendNotMatchingMessage() throws Exception {
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
static class ContextConfig extends RouteBuilder {
@Override
public void configure() {
from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
}
}
}
You can find more testing patterns illustrated in the camel-example-cdi-test
example
and the test classes that come with it.
Spring Test with XML Config Example
Here is the Spring Testing example using XML Config:
@ContextConfiguration
public class FilterTest extends SpringRunWithTestSupport {
@EndpointInject("mock:result")
protected MockEndpoint resultEndpoint;
@Produce("direct:start")
protected ProducerTemplate template;
@DirtiesContext
@Test
public void testSendMatchingMessage() throws Exception {
String expectedBody = "<matched/>";
resultEndpoint.expectedBodiesReceived(expectedBody);
template.sendBodyAndHeader(expectedBody, "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
@DirtiesContext
@Test
public void testSendNotMatchingMessage() throws Exception {
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
}
Notice that we use @DirtiesContext
on the test methods to force
Spring Testing to automatically reload the
CamelContext
after each test method - this
ensures that the tests don’t clash with each other (e.g. one test method
sending to an endpoint that is then reused in another test method).
Also notice the use of @ContextConfiguration
to indicate that by
default we should look for the
FilterTest-context.xml
on the classpath to configure the test case which looks like this:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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
">
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<filter>
<xpath>$foo = 'bar'</xpath>
<to uri="mock:result"/>
</filter>
</route>
</camelContext>
</beans>
Spring Test with Java Config Example
Here is the Spring Testing example using Java Config:
@RunWith(CamelSpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {FilterTest.ContextConfig.class}, loader = CamelSpringDelegatingTestContextLoader.class)
public class FilterTest extends AbstractJUnit4SpringContextTests {
@EndpointInject("mock:result")
protected MockEndpoint resultEndpoint;
@Produce("direct:start")
protected ProducerTemplate template;
@DirtiesContext
@Test
public void testSendMatchingMessage() throws Exception {
String expectedBody = "<matched/>";
resultEndpoint.expectedBodiesReceived(expectedBody);
template.sendBodyAndHeader(expectedBody, "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
@DirtiesContext
@Test
public void testSendNotMatchingMessage() throws Exception {
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
@Configuration
public static class ContextConfig extends SingleRouteCamelConfiguration {
@Override
@Bean
public RouteBuilder route() {
return new RouteBuilder() {
public void configure() {
from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
}
};
}
}
}
For more information see Spring Java Config.
This is similar to the XML Config example above except that there is no
XML file and instead the nested ContextConfig
class does all of the
configuration; so your entire test case is contained in a single Java
class. We currently have to reference by class name this class in the
@ContextConfiguration
which is a bit ugly. Please vote for
SJC-238 to address this
and make Spring Test work more cleanly with Spring JavaConfig.
It’s totally optional but for the ContextConfig
implementation we derive
from SingleRouteCamelConfiguration
which is a helper Spring Java
Config class which will configure the CamelContext for us and then
register the RouteBuilder we create.
Since Camel 2.11.0 you can use the CamelSpringJUnit4ClassRunner
with
CamelSpringDelegatingTestContextLoader
like
example
using Java Config with CamelSpringJUnit4ClassRunner
:
Since Camel 2.18.0 CamelSpringJUnit4ClassRunner
is deprecated. you can use the CamelSpringRunner
@RunWith(CamelSpringRunner.class)
@ContextConfiguration(
classes = {CamelSpringDelegatingTestContextLoaderTest.TestConfig.class},
// Since Camel 2.11.0
loader = CamelSpringDelegatingTestContextLoader.class
)
@MockEndpoints
public class CamelSpringDelegatingTestContextLoaderTest {
@EndpointInject("mock:direct:end")
protected MockEndpoint endEndpoint;
@EndpointInject("mock:direct:error")
protected MockEndpoint errorEndpoint;
@Produce("direct:test")
protected ProducerTemplate testProducer;
@Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
@Bean
@Override
public RouteBuilder route() {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:test").errorHandler(deadLetterChannel("direct:error")).to("direct:end");
from("direct:error").log("Received message on direct:error endpoint.");
from("direct:end").log("Received message on direct:end endpoint.");
}
};
}
}
@Test
public void testRoute() throws InterruptedException {
endEndpoint.expectedMessageCount(1);
errorEndpoint.expectedMessageCount(0);
testProducer.sendBody("<name>test</name>");
endEndpoint.assertIsSatisfied();
errorEndpoint.assertIsSatisfied();
}
}
Spring Test with XML Config and Declarative Configuration Example
Here is a Camel test support enhanced Spring Testing example using XML Config and pure Spring Test based configuration of the Camel Context:
@RunWith(CamelSpringRunner.class)
// must tell Spring to bootstrap with Camel
@BootstrapWith(CamelTestContextBootstrapper.class)
@ContextConfiguration()
// Put here to prevent Spring context caching across tests and test methods since some tests inherit
// from this test and therefore use the same Spring context. Also because we want to reset the
// Camel context and mock endpoints between test methods automatically.
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class CamelSpringRunnerPlainTest {
@Autowired
protected CamelContext camelContext;
@EndpointInject(value = "mock:a")
protected MockEndpoint mockA;
@EndpointInject(value = "mock:b")
protected MockEndpoint mockB;
@Produce(value = "direct:start")
protected ProducerTemplate start;
@Test
public void testPositive() throws Exception {
assertEquals(ServiceStatus.Started, camelContext.getStatus());
mockA.expectedBodiesReceived("David");
mockB.expectedBodiesReceived("Hello David");
start.sendBody("David");
MockEndpoint.assertIsSatisfied(camelContext);
}
@Test
public void testJmx() throws Exception {
assertEquals(DefaultManagementStrategy.class, camelContext.getManagementStrategy().getClass());
}
@Test
public void testShutdownTimeout() throws Exception {
assertEquals(10, camelContext.getShutdownStrategy().getTimeout());
assertEquals(TimeUnit.SECONDS, camelContext.getShutdownStrategy().getTimeUnit());
}
@Test
public void testStopwatch() {
StopWatch stopWatch = StopWatchTestExecutionListener.getStopWatch();
assertNotNull(stopWatch);
assertTrue(stopWatch.taken() < 100);
}
@Test
public void testExcludedRoute() {
assertNotNull(camelContext.getRoute("excludedRoute"));
}
@Test
public void testProvidesBreakpoint() {
assertNull(camelContext.getDebugger());
}
@Test
public void testRouteCoverage() throws Exception {
// noop
}
}
Notice how a custom test runner is used with the @RunWith
annotation
to support the features of CamelTestSupport
through annotations on
the test class. See Spring Testing for a list
of annotations you can use in your tests.
Blueprint Test
Here is the Blueprint Testing example using XML Config:
Also notice the use of getBlueprintDescriptors
to indicate that by
default we should look for the
camelContext.xml
in the package to configure the test case which looks like this:
Testing endpoints
Camel provides a number of endpoints which can make testing easier.
Name | Description |
---|---|
For load & soak testing this endpoint provides a way to create huge numbers of messages for sending to Components and asserting that they are consumed correctly |
|
For testing routes and mediation rules using mocks and allowing assertions to be added to an endpoint |
|
Creates a Mock endpoint which expects to receive all the message bodies that could be polled from the given underlying endpoint |
The main endpoint is the Mock endpoint which allows expectations to be added to different endpoints; you can then run your tests and assert that your expectations are met at the end.
Stubbing out physical transport technologies
If you wish to test out a route but want to avoid actually using a real physical transport (for example to unit test a transformation route rather than performing a full integration test) then the following endpoints can be useful:
Name | Description |
---|---|
Direct invocation of the consumer from the producer so that single threaded (non-SEDA) in VM invocation is performed which can be useful to mock out physical transports |
|
Delivers messages asynchronously to consumers via
a
|
|
Works like SEDA but does not validate the endpoint URI, which makes stubbing much easier. |
Testing existing routes
Camel provides some features to aid during testing of existing routes where you cannot or will not use Mock etc. For example you may have a production ready route which you want to test with some 3rd party API which sends messages into this route.
Name | Description |
---|---|
NotifyBuilder |
Allows you to be notified when a certain condition has occurred. For example when the route has completed 5 messages. You can build complex expressions to match your criteria when to be notified. |
AdviceWith |
Allows you to advice or enhance
an existing route using a |