mvn camel:run
is not a good idea generally. The right place to test a route is a unit test.
Apache Camel comes with a very sophisticated test framework. It's great, no doubt about it! But, I wanted something more.
I saw many examples published on many sites where people copied and pasted the original routes modifying from source and adding mock endpoints. I was scared particularly by one quite complex route defined in XML configuration file. The test route was almost a copy and paste. Almost, because it got unsynchronised somewhere during the development (I guess) and the test route was different from the original one. This approach seemed to me to be very error-prone.
I wanted not to copy and paste my routes, but automatically modify them slightly and then test. I wanted to dynamically change my original routes instead of copy and pasting parts of the original ones. Here is what I intended to do:
- dynamically override from endpoint and manually fire the route directly from my test;
- dynamically add mock endpoint at the end of the route so that I can verify if the messages were ok.
See how I did it.
Prerequisites
For this simple exercise I used project prepared as an example to my previous post: Connecting to EJBs from Camel.
Writing RouteBuilder
The project I wrote had one route defined in Spring XML configuration file. I added, for completeness, a simple
JUnit3-based CamelTestSupport
To test my route builder I decided to use
The source code looked like this:
Dynamically changing RouteBuilders
So what I was doing here? I simply was changing
It looked like this (most delegate methods removed from the listing as they were simply generated by the Eclipse):
When I ran the test I saw in my console:
Dynamic test passed.
JUnit4 with Spring Test Framework
For testing XML-defined routes I chose the
I wanted to test the Spring-based EJB connection route. I wrote:
When Spring test framework loads the configuration file, Camel context is instantiated and all routes are started (by default all routes start automatically). That is why in autowired
Test passed.
Source code download
The source code download can be found here: Apache-Camel-Dynamic-Tests.zip. To run this project you will also need SLSB I created as an example to this post: Connecting to EJBs from Spring.
Note: First package the project but skip the tests (in Maven to skip tests add
Summary
Now you know how to dynamically change routes. This turned out to be very useful for me. Hope you will find it useful too.
cheers,
Łukasz
For this simple exercise I used project prepared as an example to my previous post: Connecting to EJBs from Camel.
Writing RouteBuilder
The project I wrote had one route defined in Spring XML configuration file. I added, for completeness, a simple
RouteBuilder:public class ExampletRouteBuilder extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer://bar?fixedRate=true&period=5000").
setBody().constant("Szakuł").
to("log:JustASimpleTest");
}
}JUnit3-based CamelTestSupport
To test my route builder I decided to use
CamelTestSupport component. It extends junit.framework.TestCase and adds Camel dependency injection mechanisms.The source code looked like this:
public class RouteBuilderTest extends CamelTestSupport {
@EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
@Produce(uri = "direct:start")
protected ProducerTemplate template;
public void testSendMatchingMessage() throws Exception {
String expectedBody = "Szakuł";
resultEndpoint.expectedBodiesReceived(expectedBody);
// fire the route
template.sendBodyAndHeader(expectedBody, "not important will be overriden in route", "headers are not used in this route");
resultEndpoint.assertIsSatisfied();
}
@Override
protected RouteBuilder createRouteBuilder() {
return new MyMockResultRouteBuilder(new ExampletRouteBuilder());
}
}createRouteBuilder() method is abstract and needs to be implemented. What I did there was returning MyMockResultRouteBuilder which took, as an argument, the original ExampleRouteBuilder.Dynamically changing RouteBuilders
MyMockResultRouteBuilder looked like this:public class MyMockResultRouteBuilder extends MockResultRouteBuilderAdapter {
public MyMockResultRouteBuilder(RouteBuilder routeBuilder) {
super(routeBuilder);
}
@Override
public void changeRoutes() {
RouteDefinition route = this.routeBuilder.getRouteCollection().getRoutes().get(0);
// dynamically change input to direct:start
FromDefinition input = route.getInputs().get(0);
input.setUri("direct:start");
// dynamically add mock:result endpoint
route.to("mock:result");
}
}So what I was doing here? I simply was changing
timer://bar?fixedRate=true&period=5000 to direct:start, and at the end of the route I added mock endpoint. Both these endpoints were injected into my test case. See @EndpointInject(uri = "mock:result") and @Produce(uri = "direct:start") annotations. MockEndpoint allowed me to test messages.MyMockResultRouteBuilder was extending MockResultRouteBuilderAdapter. It was a simple delegate adapter with 2 key changes:- it defined empty
changeRoutes()method - after calling delegate's
addRoutesToCamelContext()it called thechangeRoutes()method
It looked like this (most delegate methods removed from the listing as they were simply generated by the Eclipse):
public class MockResultRouteBuilderAdapter extends RouteBuilder {
protected RouteBuilder routeBuilder;
public MockResultRouteBuilderAdapter(RouteBuilder routeBuilder) {
this.routeBuilder = routeBuilder;
}
// this method is called just after the routes are created
// add your code here if you need to change routes
// by default it does nothing
public void changeRoutes() {
}
// this method is never called in our case
@Override
public void configure() throws Exception {
}
public void addRoutesToCamelContext(CamelContext context) throws Exception {
routeBuilder.addRoutesToCamelContext(context);
// after the routes are added to camel context
// change them (if required)
changeRoutes();
}
public ValueBuilder bean(Class beanType, String method) {
return routeBuilder.bean(beanType, method);
}
public ValueBuilder bean(Class beanType) {
return routeBuilder.bean(beanType);
}
...
...
...
}When I ran the test I saw in my console:
[ main] DefaultCamelContext INFO Route: route1 started and consuming from: Endpoint[direct://start] [ main] DefaultCamelContext INFO Total 1 routes, of which 1 is started. [ main] DefaultCamelContext INFO Apache Camel 2.x-fuse-SNAPSHOT (CamelContext: camel-1) started in 0.125 seconds [ main] JustASimpleTest INFO Exchange[ExchangePattern:InOnly, BodyType:String, Body:Szakuł] [ main] MockEndpoint INFO Asserting: Endpoint[mock://result] is satisfied [ main] RouteBuilderTest INFO Testing done: testSendMatchingMessage(org.xh.studies.camel.test.ejb.RouteBuilderTest)
Dynamic test passed.
JUnit4 with Spring Test Framework
For testing XML-defined routes I chose the
AbstractJUnit4SpringContextTests component. It leverages the Spring Test Framework (I wrote about it nearly 2 years ago here: Spring 2.5 and Spring Test Framework) plus it adds Camel dependency injection mechanism (of course :) ).I wanted to test the Spring-based EJB connection route. I wrote:
@ContextConfiguration(locations = "/META-INF/spring/camel-context.xml")
public class SpringXMLRouteTest extends AbstractJUnit4SpringContextTests {
protected CamelContext camelContext;
@EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
@Produce(uri = "direct:start")
protected ProducerTemplate template;
@Autowired
protected void setCamelContext(CamelContext ctx) throws Exception {
camelContext = ctx;
// there are 2 routes defined in camel-context.xml
// the XML-defined is the first one here
// be careful with indexing your routes
RouteDefinition route = camelContext.getRouteDefinitions().get(0);
camelContext.stopRoute(route);
// dynamically change input to direct:start
FromDefinition input = route.getInputs().get(0);
input.setUri("direct:start");
// dynamically add mock:result endpoint
route.to("mock:result");
camelContext.startRoute(route);
}
@Test
public void testSendMatchingMessage() throws InterruptedException {
String expectedBody = "Hello Łukasz! How are you?";
resultEndpoint.expectedBodiesReceived(expectedBody);
// fire the route
template.sendBodyAndHeader(expectedBody,
"not important will be overriden",
"headers are not required in this example");
MockEndpoint.assertIsSatisfied(camelContext);
}
}When Spring test framework loads the configuration file, Camel context is instantiated and all routes are started (by default all routes start automatically). That is why in autowired
setCamelContext() method I first stop the route, modify it and then start it again. When I ran the test I saw the following output in my console (note the messages about stopping and starting routes, Camel prints the from endpoint):[ Camel Thread 0 - ShutdownTask] DefaultShutdownStrategy INFO Route: route1 shutdown complete, was consuming from: Endpoint[timer://foo?fixedRate=true&period=5000] [ main] DefaultShutdownStrategy INFO Graceful shutdown of 1 routes completed in 0 seconds [ main] DefaultCamelContext INFO Route: route1 stopped, was consuming from: Endpoint[timer://foo?fixedRate=true&period=5000] [ main] DefaultCamelContext INFO Route: route1 started and consuming from: Endpoint[direct://start] [ main] GreeterPayload INFO Exchange[ExchangePattern:InOnly, BodyType:String, Body:Łukasz] [ main] GreeterResponse INFO Exchange[ExchangePattern:InOnly, BodyType:String, Body:Hello Łukasz! How are you?] [ main] MockEndpoint INFO Asserting: Endpoint[mock://result] is satisfied [ Thread-3] DefaultCamelContext INFO Apache Camel 2.x-fuse-SNAPSHOT (CamelContext:camel-1) is shutting down
Test passed.
Source code download
The source code download can be found here: Apache-Camel-Dynamic-Tests.zip. To run this project you will also need SLSB I created as an example to this post: Connecting to EJBs from Spring.
Note: First package the project but skip the tests (in Maven to skip tests add
-Dmaven.test.skip=true) and copy the archive to OpenEJB's apps directory. Run the OpenEJB standalone server and then execute tests.Summary
Now you know how to dynamically change routes. This turned out to be very useful for me. Hope you will find it useful too.
cheers,
Łukasz

0 comments:
Post a Comment