Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Guide for Camel Blueprint JUnit Testing

DZone's Guide to

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.

· Integration Zone
Free Resource

Modernize your application architectures with microservices and APIs with best practices from this free virtual summit series. Brought to you in partnership with CA Technologies.

For 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";
       }
}



The Integration Zone is proudly sponsored by CA Technologies. Learn from expert microservices and API presentations at the Modernizing Application Architectures Virtual Summit Series.

Topics:
camel ,blueprint ,junit ,software testing ,xml ,tutorial ,integration

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}