Testing with Apache Camel

Updated:

Today I want to talk about testing. (Crowd runs away screaming)

Panic not. You can actually unit test your Camel routes.

In this article I’ll go how to set up unit tests for your Camel routes, some of the testing APIs you can use, and some advice on how to write good tests.

How to write great unit tests

Firstly, a refresher on writing unit tests!

When you write unit tests, you want to make sure they’re useful to you and the future maintainers of the code that you write today.

I always try to remember that:

  • Unit tests should test a contract – in Camel terms, that means testing everything in-between the “edges” of your routes.

    This means test the logic of your Camel routes

  • Unit tests should run in isolation – this means that the outcome of one test shouldn’t affect another.

    Or, to put it another way: running your tests in a different order shouldn’t have any impact on their success.

  • Unit tests should be maintained as your code evolves – once you write a test, make sure you keep it maintained as your code grows.

    That means don’t be tempted to use -DskipTests=true! Naughty!

  • Future developers will look at unit tests to find out how your code works - Unit tests can be so much better than documentation in describing how a piece of code works. So make an effort to write sensible, readable tests.

With those principles in mind, let’s have a look at how to set up tests for your Camel apps.

Setting up Camel tests

Camel testing with Spring Boot

Camel Blueprint testing

If you’re using the Blueprint dependency injection framework – because you’re deploying into an OSGi container like Apache ServiceMix – then you can use Camel’s Blueprint test support.

First, you need to add camel-test-blueprint as a dependency:

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

Then, you need to write a test class which extends CamelBlueprintTestSupport.

In your test class, you’ll need to override the method getBlueprintDescriptor. Return the path to your Blueprint XML definition that defines your Camel Context. This tells Camel where to find your Blueprint file, so it can start the Camel Context.

Here’s an example to get you started:

import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;

public class MyCamelAppTest extends CamelBlueprintTestSupport {

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

  @Test
  public void testContextOk() throws Exception {
    // A very simple test...
    // We test that the Camel Context starts correctly. (so our XML is valid!)
    // `context` is the injected CamelContext, provided by CamelBlueprintTestSupport
    assertTrue(context.getStatus().isStarted());
  }  

}

If you also want to inject some configuration properties because you’re using the OSGi Config Admin service, then override the loadConfigAdminConfigurationFile method. This tells Camel Blueprint Test where your test properties are located:

@Override
protected String[] loadConfigAdminConfigurationFile() {
  return new String[] {
    "src/test/resources/etc/properties.cfg",  // path to your file
    "my-config-pid" // the config admin 'PID' name
  };
}

Camel testing essential APIs

Now you’ve set up your skeleton test class, I’m going to run through some of the features of the Camel Test Framework which you can use in your tests.

Using Mock endpoints

First identify a MockEndpoint that you will be observing. Then, set some assertions on that endpoint – like, how many messages you expect to arrive there, or what the content of the messages should be:

MockEndpoint mock = getMockEndpoint("mock:output");

// Assert that 1 message will be received
mock.expectedMessageCount(1);

// Assert that 1 message will be received, with the body 'Hiya!' (oh hiya!)
mock.expectedBodiesReceived("Hiya");

Then send a message to an endpoint…:

// 'template' is a ProducerTemplate, provided by CamelTestSupport
template.sendBody("direct:start", "Hiya");

End your test case with an assertion, like one of these:

// Assert the endpoint is satisfied within 5 seconds
assertIsSatisfied(5L, TimeUnit.SECONDS, mock);

Sending data to endpoints

Sending data to an endpoint from a file:

template.sendBody("direct:start", new File("src/test/data/chickens.xml"));

Camel testing best practices

I thought it was worth sharing my own personal best practices. These are the practices I use when writing tests for Camel:

  • Divide complex routes down into smaller ones, and join them together, using Direct endpoints or similar glue. This makes it easier for you to test just your business logic.

    For example - with the routes below, we can just test the processCustomer route in isolation, without having to worry about the web service call, or the File component:

    from("direct:start")
        .split()
        // Call our Direct endpoint here
        .to("direct:processCustomer")
        .to("http://example.com/webservice")
        .to("file:customers/processed");
    
    // Now we can more easily test this route
    from("direct:processCustomer")
        .bean(ProcessCustomer.class)
        .bean(SaveCustomer.class);
    
  • Replace endpoints with mocks - mock endpoints in Camel really give you superpowers. They come with all sorts of methods to test whether a message has arrived, whether it matches certain criteria, whether messages have arrived in the right order… You could write all these tests yourself, or you could just use mocks. Seriously, just use mocks.

    This quick example shows what’s possible using just a small portion of the API. It’s easy to read, and very quick to write:

    MockEndpoint mock = getMockEndpoint("mock:result");
    mock.expectedMessageCount(1);
    mock.expectedBodiesReceived("Hello, world!");
    
    // Verify that the right message arrived within 60 seconds
    assertMockEndpointsSatisfied(60, TimeUnit.SECONDS);
    

    And how do you put mocks in your routes without rewriting your actual route code? Well…

  • Make your endpoints configurable - instead of hard-coding endpoint URIs in your routes, use placeholders. This gives you so much more flexibility to be able to replace your endpoints with anything you like at testing time. This is an ideal way of throwing mock endpoints into your routes.

    If you’re using Spring Boot, this is very easy. For example, in the route below, I’ve made the from and to endpoints as placeholders:

    from("{{uri.from}}")
        .bean(MyCustomBean.class)
        .to("{{uri.to}}");
    

    Then, in your test class, you can replace these with whatever endpoints you like:

    @SpringBootTest(classes = YourApplication.class, properties = {
        "uri.from = direct:start",
        "uri.to   = mock:output" })
    
  • Don’t write tests for Camel functionality - for example, don’t write a test which checks whether Camel can write a file, or whether it can connect to ActiveMQ. Camel’s already tested all of that in its source code!

    It’s better to focus on testing your business logic and your code. For example, test whether a customer is flagged correctly based on their loyalty status, or that your order validation works.

  • Write an additional test whenever you fix a bug or find an edge case - use bug fixing as an opportunity to write a new test, so that the same bug cannot be reintroduced.

    @Test
    public void testCustomerWithNoAddressIsRejected() {
      //...write stuff here
    }
    

    Have you got your own approach to testing Camel routes? Share it in the comments!

Leave a Comment

You can use Markdown in your comment. To write code, indent lines by 4 spaces.