2010-01-15

Apache Camel 2.2 - Improved Test Kit

In this blog post I want to announce two new features in the Camel Test Kit which makes unit testing Camel routes easier. They are geared towards unit testing existing Camel routes where you may not use mocks or the likes. In other words testing production like routes.

The two features are:

This blog post will cover the latter of the two. The reason is that I have yet to create wiki documentation for the former. So many things to do :)

NotifierBuilder
NotifierBuilder is as its name implies a builder which allows you to build an expression about a future condition where you want to be notified. Suppose you are doing integration testing and want to test when using a client API to send message, that those messages have been received and are routed as expected. Since we use a client API its not a Camel API which sends the message. Which means we have less control of this. And as we do not have mocks in the route we cannot easily know when or how the messages was routed. The NotifierBuilder helps you with this. Basically you build an expression which tells you, when this endpoint have received 3 messages and that endpoint have processed this special message then ring the bell.

A basic example
Lets start with a very basic example which just rings when one message has been processed.

NotifierBuilder notifier = new NotifierBuilder().whenDone(1).create();


client.sendMessages();


boolean done = notifier.matches(10, TimeUnit.SECONDS);
assertTrue("Should process the message within 10 seconds", done);

As you can see from this basic example, we created a notifier with the expression whenDone(1) which means that when 1 message has been processed. Then we use the client to send the messages or what ever it do to get the ball rolling. The point is that it may not be the Camel API sending in those messages.

Then we want to wait for the bell to ring, which we use the matches method on the notifier. To not wait forever we can pass in a timeout, which in this case is 10 seconds. Then we assert that a message was processed. Now we can do additional testing to verify the message was processed as expected. For example checking a database if the message was supposed to update something in it etc.

You are allows to invoke the matches method on the notifier as many times you want. So you can also use it to test that it should not match etc.

Another example
This is just a quick example to show the builder in action as it has many more methods to offer.

NotifierBuilder notifier = new NotifierBuilder()
        .from("jms:queue:orders").whenDone(5)
        .and().from("file:orders").whenDone(3)
        .and().from("jms:topic:audit").whenReceived(8)
        .create();



As you can see the builder allows you to stack expressions together which in this case we got 3 of them.

Combing the power with the Mock
You can even combine it with the power from the Camel Mock component, which allows you to use mocks for fine grained expectations. For example we expect those 3 messages to arrive in any order coming in on any JMS endpoint. 

MockEndpoint mock = getMockEndpoint("mock:foo");
mock.expectedBodiesReceivedInAnyOrder("Hello World", "Bye World", "Hi World");


NotifierBuilder notifier = new NotifierBuilder(context)
         .from("jms*").whenReceivedSatisfied(mock)
         .create();

To do this we can use the Mock for the fine grained expectations. The mock:foo is just a pseudo name as the route does not have any mocks for real. So you can give it any name you like. Notice how we use wildcard matching on the from to match any kind of JMS endpoint. Its the same wildcard matching as with the interceptors so you can use reg exp as well.


Want to learn more
You can read more about the NotifierBuilder and see which methods it currently offers at in the documentation. You can read in general about testing with Camel here.


And I also have to stress that chapter 6 in the Camel in Action book is devoted to the art of testing with Camel. So you got 35 good pages on this subject.


As its a new feature introduced in Camel 2.2 then we would of course improved it over time. And if you have ideas or feel a method in the builder is missing then let us know. And as always we love contributions and patches. And as always naming is hard, if you have better ideas for method names on the builder then again feedback is welcome.


Oh and the adviceWith is also a promising feature which I later will get documented as well. As always you can check out the source code to learn more :)

Update: NotifierBuilder has been renamed to NotifyBuilder. Which means the updated link to the documentation will be http://camel.apache.org/notifybuilder.html when the Apache wiki is synched.

5 comments:

Johan said...

Hi,

Great stuff! I have a suggestion though. I'd like some sort of filtering functionality as well. Eg:

notifer.from("jms:queue:orders").filter().xpath("/person[@name='James']")).whenDone(5).create();

/Johan

Claus Ibsen said...

You can use Mock for filtering as it allows much more elaborate ways of setting predicates for that.

But yeah I agree we should improved it to express conditions such as when those 5 James messages is done.

Currently what it lacks is to combine those two predicates in the same statement. So we need a *andWhen()* for that.

Claus Ibsen said...

I have added the filter now.

Johan said...

Awesome!

Just one thing. I've noticed that CamelContext#hasEndpoint(String uri) requires a normalized URI. In my case I specify e.g. "seda:inbound?concurrentConsumers=5" in a properties file and inject this string to my test case. However if I pass this string to hasEndpoint(..) it returns false because it requires a normalized URI. Is the same thing required for ".from("jms:queue:orders")" in your example? I would suggest that the URI is always normalized when you pass it to both from and hasEndpoint so that you don't need to do that yourself.

Claus Ibsen said...

Well spotted I have fixed that as well now.

Yes the from() will normalize the URI as well since it uses the EndpointHelper for pattern matching.