Ride the Camel!

or whatever ...

Flexible Filebased-Testing in Apache Camel

Introduction

Well it’s been “long time no see”, but finally I got to do some cameleering again ;-).

In my most recent project, I used a Test-Setup for testing a Camel-Route, I came to think, it may be worth sharing:

So, Let’s Start:

The actual work of Camel-Route that is to be tested (from now on called: Working-Route) is actually not important to understand the blog. But you need to know, that the route is servlet that wraps SOAP-Service-Calls. It transforms schemes and the Transformation-Result at the output-endpoint has to be validated.

For the Unittest of the Camel-Route I have some Testsamples organized in Files. The idea is now to read the files with another route and send them to the Files as new Exchanges to the servlet-input-Endpoint the Working-Route exposes. In the Test-Setup, the Working-Route sends it’s (transformed) Exchanges to a instantiated Validation-Route (configured to test for the transformed scheme), filling in for the SOAP-Service behind. The Response that goes through the initiating “file-reading”-Route wiretaps also to another instance of the Validation-Route testing for the backward-transformed Scheme.

File-Route

The File-Route looks like this: The “identifier” in the following code helps to the identify the Scheme to test for in this instance of the route.

    from("file:" + routeFolder + "?noop=true&readLock=rename&filter=#testfileFilter")                   // (*1)
           .autoStartup(false)                                                                          // (*2)
        .routeId("fileread" + identifier)
        .throttle(4)                                                                                    // (*3)
        .convertBodyTo(String.class)
        .wireTap("file://target/output/"+identifier)                                                    // (*4)
        .choice()                                                                                       // (*5)
            .when(simple("${in.body} not contains 'soap:Envelope'"))
                .setBody().simple(TransformationConstants.soapStart + "${in.body}" +             
                                                                      TransformationConstants.soapEnd)
        .end()
        .to(transformationProxyIN)                                                                      // (*6)
        .to(validateendpoint)                                                                           // (*7)
        .setHeader("CamelFileName", simple("${file:name.noext}_TransformationResult.${file:name.ext}")) // (*8)
        .to("file://target/output/"+identifier)                                                         // (*9) 

Explanation:
(1) The Fileroute is defined to read from a “routefolder” but doesn’t change or remove files (like the Camel-File-Consumer would in default)
A filter makes sure only Testfiles are read. If noop is on, the file-component automatically uses a idempotent-repository to ensure the same file is not read more than once.
(
2) Any instance of this Route is not active from the get-go. Each Unit-Test uses its own folder, starts the correspondent Route, so only the folder with the associated Testcases are read
(3) For Performance-Reasons on the Test-Machine, the parallel-processing of files is throttled to 4 in parallel.
(
4) A Requirement for the test is to direct the input & the output of the whole processing to a specific-folder in target. (Mainly to be able to compare them if not rightfully converted). This command-config directs a copy to the folder via the wiretap-EIP.
(5) If the sample-File doesn’t contain a SOAP-Header (Well, some Files do, some don’t, you’ll never know ;-)) the sample gets embedded into a soap-envelope
(
6) Sends the file to the Working-Route
(7) Is feeded with the response of the Working-Route and sent to the Validation-Route
(
8) Changes the CamelFileName-Header, which is used if the file-component is ever used again, which in fact is in 9.
Isn’t the “simple”-language a cute little thing. See how easy it is to change a file based on an old one.
(
9) See *4.s.. Sends the result to the same folder like the original read file.

Validation-Route

The Validation-Route has two interesting aspect - the Route that validates against a Scheme. And the errorhandling for validation-failures. Validation failures get send to the “mock:failure” endpoint. From this MockEndpoint the Unittest is able to evaluate the testresult.

 /**
  *  The "validationroute" configured in this Method, strips the soap-parts off the message and validates it against the xsd
  *  if validation succeeds, the Message-Exchange is sent to MockEndpoint "success".
  *  If Validation fails, a ValidationException is thrown and handled by the configured ErrorHandling
  * @throws Exception
  */
 @Override 
 public void configure() throws Exception {
     Namespaces soapNS = new Namespaces("s", "http://schemas.xmlsoap.org/soap/envelope/");

     onException(ValidationException.class)
         .maximumRedeliveries(0)
         .handled(true)
         .to("mock:failedValidation");

     from(validateendpoint)
         .routeId("validate" + identifier)
         .setBody().xpath("/s:Envelope/s:Body/*[1]", soapNS)
         .to("validator:" + schemaLocation)
         .wireTap("mock:success")
         .process(new ResponseFileProcessor())
         .setBody().simple(TransformationConstants.soapStartTixBank + "${in.body}" + TransformationConstants.soapEnd);
     }
 } 

Unit-Test

The test itself is relatively simple: - Count the Files (so you know how many results to expect - another requirement: If there are new samples put into folder, tests and assertions should work without changing code) - Start the file-reading route. - Assert the results from mock:success and mock:failedValidation - and finally stop the file-reading route

    @Test
    public void testBasicTransformationFunctionalityIN_10() throws Exception {
        int files = countFiles("filereadV10");
        camelContext.startRoute("filereadV10");
        validationAsserts(files, 0);
        camelContext.stopRoute("filereadV10");
    }

It may look a bit tricky and strange not to use java.io.File for this. It was just a try, but it worked, so i kept it, because it was the solution, that made the least work. You can use a Endpoint-Definition (that I already had for the filereading-route) to look how many exchanges may wait there for me. And then I used it to find out how many positive result have to be asserted for a successful test.

See the following piece of code:

    private int countFiles(String routeId) {
        BrowsableEndpoint browsableEndpoint = (BrowsableEndpoint) camelContext.getEndpoint(
                camelContext.getRoute(routeId).getConsumer().getEndpoint().getEndpointUri());
        return browsableEndpoint.getExchanges().size();
    }

The good thing about this implementation is also: it counts only the files the fileroute would also include. So let’s say you have a filefilter in place that takes only the xml-files. The BrowsableEndpoint respects that.

That’s it folks, hope you find it useful.