Camel is a great library but it also comes with a great tooling. Except the well known Standalone, CDI and Spring support, it comes with a blueprint test support module which greatly helps to test your routes without launching a full container with pax-exam or an equivalent solution.

The first thing to do is to add the right dependency, it is as simple as adding:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test-blueprint</artifactId>
    <version>${camel.version}</version>
    <scope>test</scope>
</dependency>

Then all the magic is done through CamelBlueprintTestSupport base class - it uses the same pattern than any camel test support module.

To enable to launch your blueprint route, you must override getBlueprintDescriptor method and return the descriptor(s) to deploy:

public class MyBlueprintCamelTest extends CamelBlueprintTestSupport {
    @Override
    protected String getBlueprintDescriptor() {
        return "OSGI-INF/blueprint/my-context.xml";
    }

    @Test
    public void myTest() {
       // do test
    }
}

If you need to deploy multiple blueprint contexts, just put them all in the String separated by a comma:

public class MyBlueprintCamelTest extends CamelBlueprintTestSupport {
    @Override
    protected String getBlueprintDescriptor() {
        return "OSGI-INF/blueprint/my-context1.xml,OSGI-INF/blueprint/my-context2.xml";
    }

    // ...
}

Then to trigger a route you can reuse the standard camel patterns. Assuming you can directly send a message to your from endpoint - it is the case for sedadirect, .... endpoints - then you can directly use the protected fields of the parent class to do so, creating an exchange and sending it through the camel template:

final Exchange exchange = new DefaultExchange(context);
exchange.setIn(......); // set your incoming data
template.send("seda:route_entry_point", exchange);

I will not detail more how to mock endpoints in tests etc, because the context of this post is really more blueprint than camel test support but keep in mind that you can replace endpoints if you need to simplify the testing, you don't need to rewrite the new context or create a test context which would require to maintain it to guarantee your tests are still accurate.

At that point it can look like we are done, but actually there are other methods you can override which greatly helps.

The first one is a method enabling to set some configuration in the context of the test - ConfigAdmin API which is often used through aries placeholders. To initialize a bundle with some properties, override setConfigAdminInitialConfiguration. The method takes the properties as parameter, you can provision them as you need, and the returned value is the configuration pid (name of the file in etc/ without the cfg extension:

@Override
protected String setConfigAdminInitialConfiguration(final Properties props) {
  props.setProperty("my.config.property", "and its value");
  return "org.company.my.config";
}

Where this becomes very very powerful if when you use a JUnit rule which has some dynamic values like a port. For instance to test a blueprint module connecting to Elasticsearch which is launched in the test with a random port you can ensure your blueprint container uses the right port this way:

public class ElasticsearchSenderProcessorTest extends CamelBlueprintTestSupport {
    @ClassRule(order = 1) // rule creating random ports
    public static final RandomPort ES_PORTS = new RandomPort("http", "tcp");

    @ClassRule(order = 2) // rule launching ES and respecting the random ports of the first rule
    public static final Elasticsearch ES = new Elasticsearch(ES_PORTS::getPorts);

    @Override
    protected String getBlueprintDescriptor() {
        return "OSGI-INF/blueprint/my-context.xml";
    }

    @Override
    protected String setConfigAdminInitialConfiguration(final Properties props) {
        props.setProperty("elasticsearch.protocol", "http");
        props.setProperty("elasticsearch.host", "localhost");
        props.setProperty("elasticsearch.port", Integer.toString(getHttpPort()));
        // etc...

        return "org.company.elasticsearch";
    }

    @Test
    public void myTest() {
       // ...
    }
}

It is already not bad but the blueprint integration will deploy the classpath, the test classpath, which can be an issue since it can lead to deploy way more than needed. To ensure you control the blueprint deployment, even being outside an OSGi container, camel exposes a getBundleFilter method which enables you to filter the deployed bundles. This way you can blacklist undesired bundles - or whitelist them if it is easier:

@Override
protected String getBundleFilter() {
    return "(!(Bundle-Name=org.company.tool))";
}

One concrete example is to blacklist snappy for tests where the native setup will not be done and the blueprint/OSGi activator initialization would make the test failling:

(!(Bundle-SymbolicName=org.xerial.snappy.snappy-java))

Finally the last method you can override and which is very useful is the createCamelContext one. It is quite common to have multiple contexts and in this case the default implementation takes the first one...which can not be the one you want to test. Here is a blueprint descriptor with multiple camel context:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:camel="http://camel.apache.org/schema/blueprint"
           xsi:schemaLocation="
            http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
            http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
  <camel:camelContext id="my-main-context">
    <camel:route>
      <!-- ... the routes .... -->
    </camel:route>
  </camel:camelContext>

  <camel:camelContext id="otherCamelContext">
      <camel:template id="otherTemplate" />
  </camel:camelContext>
</blueprint>

With such a setup you must provide yourself the camel context to camel test to ensure it uses the right one and not a random one.

Of course, with this kind of descriptor, CamelContext are OSGi services and camel blueprint test support provides a lookup utility in blueprint test support so you just have to lookup the right bean adding a filter on the context name/id:

@Override
protected CamelContext createCamelContext() {
  return getOsgiService(CamelContext.class, "(camel.context.name=my-main-context)");
}

getOsgiService is an utility of the base camel class we inherited to write the test so the code to explicitly select the right camel context is really as simple as previous snippet.

So now we know how to :

  • Launch a camel blueprint test,
  • Select the blueprint descriptor we want to deploy for our test,
  • Filter the deployed OSGi bundles in the context of the test,
  • Setup our configuration dynamically,
  • Make the context selection deterministic.

Most of these point are simply overriding a method from the base class of camel blueprint test support, so you don't have any excuse anymore to not test your production code ;).

From the same author:

In the same category: