Guide for Camel Blueprint JUnit Testing
If you want to develop a JUnit test class for a blueprint XML, this guide provides everything that you need to know — from overriding properties to using shared services.
Join the DZone community and get the full member experience.
Join For FreeFor Camel Blueprint testing, Camel has provided a mechanism called camel-test-blueprint
.
The JUnit class needs to inherit the CamelBlueprintTestSupport
class, which provides various functionalities to mock and test camel routes in the blueprint.
How to Develop a JUnit Test Class for a Blueprint XML
Generate a new JUnit class or use the wizard.
Extend the CamelBlueprintTestSupport
in this class.
The first important step is to load the blueprint XML in the JUnit. For this, define the getBlueprintDescriptor
method as shown below-
@Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
By default, the test container scans the test classpath for all the OSGi bundles available there. All the bundles with Blueprint descriptor files will be automatically started by the test container. Sometimes, you might see that the JUnit logs show that it is trying to send a message to an endpoint, but could not send it due to some reason and fails. When you check the endpoint name, it does not seem to a part of the blueprint that you are actually testing, but it is from some other bundle. In such cases, if you would like to prevent such bundles from being started by the test container, just filter them out. To do this, override the getBundleFilter
method as demonstrated in the snippet below:
@Override
protected String getBundleFilter() {
return "(!(Bundle-SymbolicName= org.test.junit.bundleTest))";
}
Overriding Properties
If you are using property file in your blueprint XML and wish to override it with test property values, then you can either use the test property file in your JUnit or override the specific property key values inside the JUnit as shown below:
//override specific values
@Override
protected String useOverridePropertiesWithConfigAdmin(Dictionaryprop throws Exception {
/*
treat Dictionary like a Map
*/
prop.with {
put'foo', 'bar'
}
/*
return the "persistent-id"
if your property file is config.properties, then return config
*/
return "config"
}
//replace with test property file
@Override
protected String[] loadConfigAdminConfigurationFile() {
/*first element in the array is the test property file name and second element is the actual file name that needs to be replaced*/
returnnew String[] { "src/test/resources/esb.test.info", "org.esb.info" };
}
Endpoint Handling
Blueprint routes contain a from
endpoint, and there can be multiple to
endpoints. The starting point for a route is the from
endpoint. So, the JUnit needs to either directly send the required input message to this endpoint directly, or you can mock this endpoint and then send the message to the mock endpoint. The from
’ endpoint can be mocked by declaring the ProducerTemplate
as shown below:
@Produce(uri = "vm:startRoute")
protected ProducerTemplate inputEndpoint;
There may be a situation in which in the from
endpoint can be a cCXFendpoint to invoke a service. In such cases, you can replace the CVF endpoint with a direct
endpoint, as shown below:
context.getRouteDefinition("routeName")
.adviceWith(context, new AdviceWithRouteBuilder() {
@Override
publicvoid configure() throws Exception {
//replaces the from endpoint with the endpoint given as the parameter
replaceFromWith("direct:cxfIn"); }
});
adviceWith
Sometimes, you need to configure the route flow to accommodate a specific test case scenario. For example, this is good if you want to:
Mock the endpoints so that the message does not get sent to the external endpoint and is returned back in the JUnit context.
Test only one of the
to
endpoints inside a route and skip other endpoints.Completely remove some endpoint from the test.
Add a new endpoint in the route for testing the output.
Send Request to the Route
Once the route flow is configured, we can create the input message that is required by the route to be tested. The input message can be a string, an object, or an array of objects.
The input message needs to be sent to the from
endpoint or the mock processor endpoint using the template methods, like this:
//the parameters include the endpoint name and the input message
template.requestBody("direct:foo", inputString);
//using the ProducerTemplate defined in above section
inputEndpoint.requestBody("body");
//the parameters include the endpoint name,the input message and a Map containing header key-value pairs
template.requestBodyAndHeaders("vm:foo", "Hi", headersMap));
//Using exchange to set the input
Exchange senderExchange = new DefaultExchange(context, ExchangePattern.InOut); senderExchange.getIn().setBody(request);
//the input is set as the body inside the exchange
Exchange exchange = inputEndpoint.send(senderExchange);
org.apache.camel.Message out = exchange.getOut();
String output = out.getBody(String.class);
The template methods requestBody
and sendBody
can be used to send the input to the endpoint. The difference between these two is that sendBody
can have multiple message exchange patterns (InOnly
, InOut
, etc.) as per the endpoint while as requestBody
explicitly uses request-response (InOut
) pattern. Also, requestBody
returns the response immediately to be consumed for further assertions.
Setting Header on the Input Message
We can set header values in the input message as shown below:
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("Id", "1234");
headers.put("Status", "OK");
Then, we can set this Map as a parameter in requestBodyAndHeaders(“endpoint”,”body”,headers)
.
Individual headers can also be set directly as key and value in the method parameters:
template.requestBodyAndHeader("body", "headerName", "headerValue");
Assertion
Once we get the response, it needs to be asserted against different conditions. Blueprint test support provides few assertion methods to check if the message has reached the desired mock endpoints or to check the count of messages reaching a given mock endpoint.
getMockEndpoint("mock:direct:foo").expectedMessageCount(1);
assertMockEndpointsSatisfied();//checks if the message has reached the mocked endpoints and the count satisfied as per the expectedMessageCount
assertOutMessageBodyEquals(exchange, expected);
Using Shared Services
When using camel-test-blueprint
, you may do unit tests, which requires using shared services that are not available during unit testing, but only in the real OSGi container (or example, a shared DataSource
). This may give a "Waiting for service" error.
To make it easier to register services on startup such as a standalone DataSource
or any other service, you can override the method addServicesOnStartup
in your unit test class:
@Override
protectedvoid addServicesOnStartup(Map<String, KeyValueHolder<Object, Dictionary>> services) {
KeyValueHolder serviceHolder = new KeyValueHolder(new DataSourceServiceImpl(), null);
services.put(DataSourceService.class.getName(), serviceHolder);
}
In the example below, we register service org.apache.camel.test.blueprint.MyService
using the name myService
having a property as shown below:
@Override
protected void addServicesOnStartup(Map<String, KeyValueHolder<Object, Dictionary>> services) {
services.put("myService", asService(myService, "beer", "Carlsberg"));
}
The asService
is a builder method that makes it easy to register a service with a single property. If you need more properties, you can use the asService
method that takes a Dictionary as an argument. If you do not need any properties, then just pass in null:
services.put("myService", asService(myService, null));
Dependency Resolution
Sometimes, JUnit might require few dependency JARs. This can throw a missing dependencies exception if not resolved. To avoid this, we need to add the dependencies in the pom.xml
file with the scope limited to test.
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>test</scope>
Also, you should keep a check on the surefire plugin’s skipTest
flag so that it does not skip the test.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
Sample JUnit and Blueprint XML
Blueprint XML — Sample 1
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:camel="http://camel.apache.org/schema/blueprint"
xmlns:camelcxf="http://camel.apache.org/schema/blueprint/cxf"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
xmlns:cxf="http://cxf.apache.org/blueprint/core"
xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.osgi.org/xmlns/blueprint/v1.0.0 https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd ">
<cm:property-placeholder
id="property-placeholder-fd74db31-7dc4-4206-95c4-bc0431310fe2" persistent-id="org.esb.info"/>
<camelcxf:cxfEndpoint address="/OrderCreateService"
endpointName="p:OrderCreatePort" id="cpqOAEndpoint"
serviceClass="com.comp.org.esb.connector.cpq.services.ordercreateservice.OrderCreateEndpoint"
serviceName="p:OrderCreateService"
wsdlURL="META-INF/wsdl/cpq_server.wsdl" xmlns:p="http://services.cpq.connector.esb.org.comp.com/OrderCreateService">
<camelcxf:inInterceptors>
<ref component-id="logInbound"/>
</camelcxf:inInterceptors>
<camelcxf:outInterceptors>
<ref component-id="logOutbound"/>
</camelcxf:outInterceptors>
</camelcxf:cxfEndpoint>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" id="logOutbound"/>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" id="logInbound"/>
<bean class="com.comp.org.esb.connector.cpq.CreateRequestProcessor" id="reqProcessor"/>
<bean class="com.comp.org.esb.connector.cpq.CreateResponseProcessor" id="resProcessor"/>
<bean class="com.comp.org.esb.connector.cpq.ToBPMProcessor" id="toBpmProcessor"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/blueprint">
<route id="cpqCreateOrderReq">
<from id="_from1" uri="cxf:bean:cpqOAEndpoint"/>
<log id="_log1" message="${body}"/>
<to id="_to1" uri="seda:createOrderFlow?"/>
<to id="_to2" uri="bean:resProcessor"/>
</route>
<route id="cpqCreateOrderFlow">
<from id="_from2" uri="seda:createOrderFlow"/>
<setHeader headerName="bpmOperationName" id="_setHeader1">
<constant>OPERATION_CREATE_ORDER</constant>
</setHeader>
<!-- set message status -->
<to id="_to3" uri="bean:toBpmProcessor"/>
<to id="_to4" uri="direct-vm:createOrderCD"/>
<log id="_log3" message="${headers}"/>
<setHeader headerName="bpmOperationName" id="_setHeader2">
<constant>OPERATION_START_PROCESS</constant>
</setHeader>
<to id="_to5" uri="bean:toBpmProcessor"/>
<to id="_to6" uri="direct-vm:startProcess"/>
<setHeader headerName="status" id="_setHeader3">
<constant>STARTED</constant>
</setHeader>
<to id="_to7" uri="direct:cpqTrack"/>
<to id="_to8" uri="direct-vm:ossInsertOrder"/>
</route>
</camelContext>
</blueprint>
JUnit Test Class — Sample 1
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.junit.Test;
import com.comp.org.esb.connector.cpq.services.InputOrderCreate;
import com.comp.org.esb.connector.cpq.services.OutputOrderCreate;
publicclass CPQBlueprintTest_CXF extends CamelBlueprintTestSupport {
@Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
@Override
protected String getBundleFilter() {
// I don't want test container to scan and load Logback bundle during the test
return "(!(.comp.org.esb.org-esb-utils))";
}
@Test
publicvoid soapUnwrapperTest_usingMock() throws Exception {
context.getRouteDefinition("cpqCreateOrderReq")
.adviceWith(context, new AdviceWithRouteBuilder() {
@Override
publicvoid configure() throws Exception {
replaceFromWith("direct:cxfIn");
}
});
context.getRouteDefinition("cpqCreateOrderFlow").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
publicvoid configure() throws Exception {
mockEndpointsAndSkip("direct-vm:ossInsertOrder");
mockEndpointsAndSkip("direct-vm:startProcess");
mockEndpointsAndSkip("direct:cpqTrack");
mockEndpointsAndSkip("direct-vm:createOrderCD");
}
});
context.start();
InputOrderCreate in = new InputOrderCreate();
in.setOrderXmlRequest("<OrderElement"> <id>0001</id> <orderType>test</orderType> <dateCreation>test</dateCreation> <agreement>OK</agreement> <status>OK</status> </OrderElement> ]]>");
OutputOrderCreate response = (OutputOrderCreate)template.requestBody("direct:cxfIn", in);
log.info("Response :"+response.getCode());
assertEquals("OK", response.getCode());
}
}
Blueprint XML — Sample 2
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:camel="http://camel.apache.org/schema/blueprint"
xmlns:camelcxf="http://camel.apache.org/schema/blueprint/cxf"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
xmlns:cxf="http://cxf.apache.org/blueprint/core"
xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.osgi.org/xmlns/blueprint/v1.0.0 https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.0.0.xsd http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd ">
<cm:property-placeholder
id="property-placeholder-26e017d6-4b44-4090-b64e-72778242fdee" persistent-id="org.esb.info"/>
<bean
class="com.comp.org.esb.connector.PasswordCallbackHandler" id="pswCallback">
<property name="propertyInfoService" ref="orgInfoService"/>
</bean>
<reference id="orgInfoService" interface="com.comp.org.esb.utils.services.OrgInfoService"/>
<bean class="com.comp.org.esb.connector.CreateRequestProcessor" id="reqProcessor"/>
<bean class="com.comp.org.esb.connector.CreateResponseProcessor" id="resProcessor"/>
<bean class="com.comp.org.esb.connector.ToBillingProcessor" id="toBillingProcessor"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/blueprint">
<route id="sendToBilling">
<from id="_from4" uri="seda:sendToBilling"/>
<convertBodyTo id="_convertBodyTo1" type="com.comp.org.esb.connector.services.Order"/>
<setHeader headerName="orderId" id="_setHeader4">
<simple>${body.id}</simple>
</setHeader>
<choice id="_choice1">
<when id="_when1">
<simple>${body.status} == 'COMPLETE'</simple>
<log id="_log13" message="${headers}"/>
<log id="_log14" message="${body}"/>
<to id="_to8" uri="bean:toBillingProcessor"/>
<to id="_to9" uri="direct-vm:forwToBilling"/>
<log id="_log15" message="${headers}"/>
<log id="_log16" message="${body}"/>
<setHeader headerName="status" id="_setHeader5">
<constant>COMPLETE</constant>
</setHeader>
<to id="_to10" uri="direct-vm:cpqTrackVm"/>
<to id="_to11" uri="direct-vm:retrieveOrderCD"/>
<setHeader headerName="status" id="_setHeader6">
<constant>BILLABLE</constant>
</setHeader>
<to id="_to12" uri="direct-vm:updateOrderStatusCD"/>
</when>
<otherwise id="_otherwise1">
<log id="_log18" message="${headers}"/>
<log id="_log19" message="${body}"/>
<setHeader headerName="status" id="_setHeader7">
<simple>${body.status}</simple>
</setHeader>
</otherwise>
</choice>
<to id="_to13" uri="direct-vm:cpqTrackVm"/>
</route>
</camelContext>
</blueprint>
Junit Test Class — Sample 2
import java.util.Dictionary;
import java.util.GregorianCalendar;
import java.util.Map;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.impl.DefaultExchange;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.apache.camel.util.KeyValueHolder;
import org.junit.Test;
import com.comp.org.esb.utils.services.OrgInfoService;
import com.comp.org.esb.utils.services.OrgInfoServiceImpl;
import com.comp.org.esb.connector.services.Order;
import com.comp.org.esb.connector.services.Status;
publicclass BPMBlueprintTest extends CamelBlueprintTestSupport {
@Produce(uri = "seda:sendToBilling")
protected ProducerTemplate inputEndpoint;
@Override
protected String getBundleFilter() {
// I don't want test container to scan and load Logback bundle during the test
return "(!(.comp.org.esb.org-esb-utils)) ";
//+ "(!(.comp.org.esb.org-esb-utils.services))";
}
@Override
protectedvoid addServicesOnStartup(Map<String, KeyValueHolder<Object, Dictionary>> services) {
KeyValueHolder serviceHolder = new KeyValueHolder(new OrgInfoServiceImpl(), null);
services.put(OrgInfoService.class.getName(), serviceHolder);
}
@Test
publicvoid testCamelRoute() throws Exception {
// Define mock
context.getRouteDefinition("sendToBilling").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
publicvoid configure() throws Exception {
mockEndpointsAndSkip("direct-vm:cpqTrackVm");
mockEndpointsAndSkip("direct-vm:retrieveOrderCD");
mockEndpointsAndSkip("direct-vm:updateOrderStatusCD");
mockEndpointsAndSkip("direct-vm:cpqTrackVm");
interceptSendToEndpoint("direct-vm:forwToBilling")
.skipSendToOriginalEndpoint()
.to("mock:advised");
}
});
// Send some messages to input endpoints
Order request = new Order();
request.setId("123");
request.setStatus(Status.COMPLETE);
Exchange senderExchange = new DefaultExchange(context, ExchangePattern.InOut);
senderExchange.getIn().setBody(request);
Exchange exchange = inputEndpoint.send(senderExchange);
org.apache.camel.Message out = exchange.getOut();
String output = out.getBody(String.class);
assertStringContains(output, "123:");
assertMockEndpointsSatisfied();
}
@Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
}
Opinions expressed by DZone contributors are their own.
Comments