Testing WebSockets with a Citrus twist

In a previous article we went through how to build a chat room web application that used REST and STOMP for communicating between the client and server. In this article I use the very same application and show how to write automated integration tests using the open source Citrus integration test framework.

If you haven’t read the first article don’t worry. A quick summary of all the important bits will be shown shortly below. But before I get to that lets talk a little bit about automated integration testing and citrus.

One of the biggest challenges when testing any application is being able to simulate all endpoints.

If you take an online web shop for example it typically interacts with numerous backend services (product catalogue, credit check, shipping, billing, etc.) during the course of processing the order. When writing an automated integration test that tests the placement of an order you’ll have to simulate each of these services. Some services may expose a REST/HTTP interface whereas others may expose a SOAP/JMS interface. In some scenarios the online web shop will be acting as a client, consuming the backend services. In other cases it will be acting as a server, processing customer requests.

The point is that testing such a scenario can be very complex. A simple application today with one or two interfaces may quickly grow into a complex application with 10s or even 100s of interfaces later on. When you look at integration test tools then don’t loose sight of this. Sure some tools are great at simulating REST interfaces. Others are great at simulating SOAP. However for me the most important criteria is to find a test tool that combines these and many other messaging protocols. The tool should be flexible and extensible. And this is where citrus comes into the equation.

Before I dive into integration testing, let’s do a quick recap on the chat room application.

Chat Room

The basic architecture of the chat room application is presented below:

We have a client-server architecture, using web sockets and REST for communicating between the client and the server. The system under test is the blue box above and I’m going to write some tests that simulate the two green arrows.

The application’s entity model is very modest, using just the two entities shown below:

A room contains a list of messages and the name of the user that created the room. A message contains some text and the name of the user that sent the message. That’s basically it.

The following REST operations can be sent from the client to the server:

  • Join or leave the chat application
  • Get the list of logged in users
  • Get the list of rooms
  • Create or remove a room
  • Send a message to a room

The server can push the following notifications to connected clients using STOMP over WebSocket:

  • Notify room created or removed
  • Notify chat message sent
  • Notify user logged-in or logged-out

Starting the application

The source code for the application is available on github.

To run the application you need to have Java 8+ and Maven 3.3.3+ installed and configured on your system.

The application comes shipped with the H2 database that you need to start firing up the application:

  • Open up a shell
  • Change to the H2 directory under ../support/h2_v1.3.176/
  • Run the H2 start-up script (either h2.bat or h2.sh).

Now you can start the application. This can be done either from the command line or from inside your favourite IDE.

From the command line type:

shell
$ mvn clean install spring-boot:run

This will start up one instance on port 8090.

Alternatively you can start the application from inside your IDE by running the following main class:

de.consol.chat.ChatApplication

Now go ahead and check that the application is running by opening the application in your browser:

http://localhost:8090/

Testing the application

All the source code I’ll be displaying below is available here: https://github.com/martinmaher/jcache-chat-citrus. I will show you how to build the project from scratch so you can either clone the above repository or just follow the instructions below, whatever you prefer.

The first thing I need to do is to create an empty maven project for storing and executing our tests. The quickest way of doing this is to execute the maven archtetype goal, which will create a citrus project with a basic pom file and some sample integration tests. This can be done as follows:

  • Create a new directory for the project.
  • Change to this directory
  • Execute: mvn archetype:generate -DarchetypeCatalog=http://citrusframework.org
  • When prompted select archetype ‘1’
  • For the groupId, artifactId and package enter whatever you like. I entered
    • groupId: de.consol
    • artifactId: chat-citrus
    • package: de.consol.chat.citrus

Assuming all goes well you’ll find the following files in the project:

  • pom.xml – contains the maven project configuration
  • src/test/resources/citrus-context.xml – contains the citrus configuration
  • src/test/resources/citrus.properties – contains global properties used by citrus
  • src/test/resources/log4j.xml – contains the log configuration used by citrus

There may be some other files like SampleJavaIT and SampleXmlIT that can be removed if you like, since I wont be using here.

Simulating the REST requests

To keep things simple I’ll start by simulating the REST requests sent by the client. To begin with lets take the requests where a user joins the chat room.

The request I want to simulate is:

POST /users/<USERNAME> HTTP/1.1
Accept: text/plain, application/json, application/*+json, */*
Content-Type: application/json;charset=UTF-8

and if all goes well I should get the response:

HTTP/1.1 200 OK

The first thing I need to do is to create a new java class for executing my test. I extend the TestNGCitrusTestDesigner class, which allows me to use Citrus’s Java DSL and add I’ll add a variable for storing a random name, which will be used for the user name when joining the chat room.

@Test
public class Test_01_RestIT extends TestNGCitrusTestDesigner {
    @CitrusTest
    public void testJoiningAndLeaving() {
        variable("username", "citrus:randomString(10, UPPERCASE)");

        echo("Joining with user: ${username}");
    }
}

If you like you can run this test and it should output “Joining with user ..” to the console.

Next I add the test actions to join the chat room.

[...]

echo("Joining with user: ${username}");
send("chatRestClient")
        .payload("")
        .http()
        .method(HttpMethod.POST)
        .path("/users/${username}");
receive("chatRestClient")
        .messageType(MessageType.JSON)
        .http()
        .status(HttpStatus.OK);

The first test action sends the request to the HTTP server. The action basically says:

  • send(“chatRestClient”) -> send using the chatRestClient
  • payload(“”) -> an empty payload
  • http() -> using the HTTP protocol
  • method(HttpMethod.POST) -> using HTTP’s POST method
  • path(“/users/${username}”) -> to URI to send the HTTP request to

The syntax ${variable-name} is a variable placeholder used in citrus that gets resolved to the value of the variable at runtime. In the example above I use it for adding the variable username to the URI.

The second action is used for verifying that the server processes the request successfully. It expects a HTTP 200 code to be returned from the server.

You may be wondering how citrus knows which host and port to send the request to. It doesn’t, well not yet. I still have to configure this. This strange string chatRestClient is instructing citrus to find an endpoint using this name in the citrus configuration and use this endpoint for sending and receiving requests.

So I’m going to add this endpoint to the citrus-context.xml file now.

<citrus-http:client id="chatRestClient"
                    request-url="http://localhost:8090/"
                    request-method="POST"
                    content-type="application/json"
                    timeout="60000"/>

I have added a new HTTP client endpoint chatRestClient that sends requests to the base url http://localhost:8090/. By default the request method POST will be used with the content-type application/json, but both of these can be overloaded in the test. After sending a request citrus will wait 60 seconds for a reply from the server before timing out. And that’s it.

Now go ahead and run this test. If you have the application opened up in your browser then you will notice a new user joins the chat room when the test executes.

To complete the test I’m going to add the test actions for leaving the chat.

[...]

echo("Leaving chat: ${username}");
send("chatRestClient")
        .payload("")
        .http()
        .method(HttpMethod.DELETE)
        .path("/users/${username}");
receive("chatRestClient")
        .messageType(MessageType.JSON)
        .http()
        .status(HttpStatus.OK);

You will notice a lot of similarities to the join test actions. The only difference is that the HTTP method DELETE is used this time instead of POST.

You can run this test again if you like and depending on how sharp your eyes are you may notice in your browser that a user joins and almost immediately leaves the chat room.

That’s it for the REST requests for the moment. Now I’m going to move onto WebSockets.

Simulating the STOMP notifications

Before I get into simulating STOMP notifications, I think its important to understand what STOMP is and how it’s used here.

STOMP is a S imple T ext O rientated M essaging P rotocol. It uses a frame-based protocol, shown below, for communicating between multiple hosts.

COMMAND
header1:value1
header2:value2

Body^@

Each frame contains a single COMMAND, zero or more HEADERS, a BODY and the null character (^@) to terminate the frame.

In the chat room application the server sends all notifications to connected clients using this protocol. It does this using the WebSocket API since it enables bidirectional communication between the web server and clients. Each time an event occurs, like a user joining the chat room, this event data is then pushed to the clients from the server.

In STOMP there are different types of COMMANDs that can be sent:

  • CONNECT: this is sent by a client to the server when it connects
  • CONNECTED: this is an acknowledgement sent by the server to a client on successful connection
  • SUBSCRIBE_: this is sent by a client to the server to indicate that it is interested in certain types of messages. The client sends a destination header to indicate which messages it is interested in.
  • MESSAGE_: this is sent by the server to the client when a destination, that the client has subscribed to, sends a message.
  • DISCONNECT_: this is sent by the client to the server when its disconnecting.

This is by no means an exhaustive list of commands but covers all commands used in the chat room application.

To put all that into perspective, this is basically what happens when the user Joe Blogs joins the chat room application:

  1. The client opens a WebSocket connection to the server
  2. The client sends a CONNECT frame to the server
CONNECT
accept-version:1.1
heart-beat:0,0
  1. The sever sends a CONNECTED frame acknowledgement back to the client
CONNECTED
version:1.1
heart-beat:0,0
  1. The client sends a SUBSCRIBE frame to the server, indicating that it is interested in messages on the destination /topic/users/joined
SUBSCRIBE
id:sub-0
destination:/topic/users/joined
  1. The user Joe Blogs joins the chat room (using the HTTP/REST interface)
  2. The server sends a MESSAGE frame to the client on the destination /topic/users/joined indicating that a user joined.
MESSAGE
destination:/topic/users/joined
content-type:application/json;charset=UTF-8
subscription:sub-0
message-id:1-18
content-length:11

"Joe Blogs"

So I’m going to try and pack all of this into a single test. I start by creating a new Java class Test_02_StompIT.

@Test
public class Test_02_StompIT extends TestNGCitrusTestDesigner {
    @CitrusTest
    public void testJoining () {
        variable("username", "citrus:randomString(10, UPPERCASE)");

        echo("Joining with user: ${username}");
    }
}

Then I add the test actions for connecting to the STOMP server.

send(chatWebSocketClient)
            .payload("CONNECT\n" +
                    "accept-version:1.1\n" +
                    "heart-beat:0,0\n" +
                    "\n" + (char)0
            );

receive(chatWebSocketClient)
                .messageType(MessageType.PLAINTEXT)
                .payload("CONNECTED\n" +
                        "version:1.1\n" +
                        "heart-beat:0,0\n" +
                        "\n" + (char)0
                );

So what is going on here?

The first test action is using the endpoint chatWebSocketClient to send the STOMP CONNECT frame to the server. The payload I just covered above only this time control characters like carriage return and the null character have been added.

The second test action is verifying the client was successfully connected. It compares the received payload from the server against the expected payload, which is specified in the test action. Only when both payloads match exactly will the action be successful.

Before I can execute my test I have to configure the chatWebSocketClient endpoint. Again this is done in the citrus-context.xml file by adding the following lines:

<citrus-websocket:client id="chatWebSocketClient"
                         url="ws://localhost:8090/chatEndpoint/websocket"
                         timeout="5000"/>

There’s nothing special here. I just added a websocket client endpoint that connects to the server on the url ws://localhost:8090/chatEndpoint/websocket.

Now you are good to go and you can run the test. If all goes well you should see a lot of console output with SUCCESS somewhere near the bottom.

Ok, so the test doesn’t really do a whole lot just yet. So I’m going to add the next test actions to subscribe to the destination /topic/users/joined, join the chat room and then finally verify that a STOMP MESSAGE frame is sent to indicate a user has joined.

echo("Subscribe to destination: /topic/users/joined");
send("chatWebSocketClient")
        .payload("SUBSCRIBE\n" +
                "id:sub-0\n" +
                "destination:/topic/users/joined\n" +
                "\n" + (char) 0
        );

echo("Joining with user: ${username}");
send("chatRestClient")
        .payload("")
        .http()
        .method(HttpMethod.POST)
        .path("/users/${username}");
receive("chatRestClient")
        .messageType(MessageType.JSON)
        .http()
        .status(HttpStatus.OK);

echo("Verify user-joined notification is received");
receive("chatWebSocketClient")
        .messageType(MessageType.PLAINTEXT)
        .payload("MESSAGE\n" +
                "destination:/topic/users/joined\n" +
                "content-type:application/json;charset=UTF-8\n" +
                "subscription:sub-0\n" +
                "message-id:6-21\n" +
                "content-length:12\n" +
                "\n" +
                "\"${username}\"" + (char) 0
        );

If you look carefully at the last test action, the MESSAGE frame, you will notice the message-id header. The server sets the value of this header dynamically so the exact payload comparison done here is bound to fail, unless of course you are incredibly lucky. So how do I get around this problem?

Thanks to citrus there are many ways to solve this problem:

  • Remove the payload verification from the test action - This just ignores the problem for the time being but doesn’t fix it.
  • Do a partial payload verification – It’s possible to just search for certain strings in the payload, ignoring the rest.
  • Unmarshall the payload, converting it into a java object and verify the individual attributes – This is slightly more complicated but offers by far the most flexibility.

For the moment I’m going to go with partial payload verification. When using the Citrus Java DSL you can easily do this with a ValidationCallback as shown below:

echo("Verify user-joined notification is received");
receive("chatWebSocketClient")
        .messageType(MessageType.PLAINTEXT)
        .validationCallback(new AbstractValidationCallback<String>() {
            @Override
            public void validate(String payload, Map<String, Object> headers, TestContext context) {
                Assert.assertTrue(payload.contains("MESSAGE\n"));
                Assert.assertTrue(payload.contains("destination:/topic/users/joined\n"));
                Assert.assertTrue(payload.contains("subscription:sub-0\n"));
                Assert.assertTrue(payload.contains(context.getVariable("username")));
            }
        });

Great. Go ahead and run the test. This time it should run successfully.

Cleaning up the test

So far I’ve covered a user joining the chat room. I still have to verify that rooms can be created, removed, messages can be sent, notifications for the above are received, etc. But before I do this lets take a step back and look at our test so far.

It’s not bad, but it could be cleaned it somewhat. There are too many concatenated strings, too many carriage returns and other funny characters. It’s too hard to read. It would be great if I could use a fluid API for generating the STOMP frames. And since I’m planning on verifying and extracting data from the received payloads it would be equally cool if this could be somehow unmarshalled. Well guess what, you can do this with citrus. This is what I was talking about earlier when I mentioned how important it is to pick an integration test tool that is flexible. It’s not that citrus supports STOMP out of the box, because it doesn’t, at least not yet. However through its flexible API it enables a tester to add support for features or behaviour that is currently not supported. And this is what I’m going to show you now when I clean up or refactor this test.

I’ll leave the other test class in place and create a new test class Test_03_StompIT, copying over the contents of the original.

I’ll begin by creating a new StompFrame class, which is just a container for holding a STOMP frame in a structured way.

public class StompFrame {
    private String command;
    private Map<String, String> headers = new LinkedHashMap<>();
    private String body;

    // getters/setters….

}

Then I add a StompFrameBuilder class that uses the builder pattern for creating STOMP frames.

public class StompFrameBuilder {
    StompFrame stompFrame = new StompFrame();

    public StompFrameBuilder withCommand(String command) {
        stompFrame.setCommand(command);
        return this;
    }

    public StompFrameBuilder withHeader(String key, String value) {
        stompFrame.addHeader(key, value);
        return this;
    }

    public StompFrameBuilder withBody(String body) {
        stompFrame.setBody(body);
        return this;
    }

    public StompFrame build() {
        return stompFrame;
    }
}

And finally I’ll add a static method to the StompFrame class to convert a STOMP frame into the expected wire format.

public static String toWireFormat(StompFrame stompFrame) {
    StringBuilder builder = new StringBuilder();

    builder.append(stompFrame.getCommand());
    builder.append("\n");

    for (Map.Entry<String, String> header : stompFrame.getHeaders().entrySet()) {
        builder.append(header.getKey());
        builder.append(":");
        builder.append(header.getValue());
        builder.append("\n");
    }

    if(stompFrame.hasBody()) {
        builder.append(stompFrame.getBody());
        builder.append("\n");
    }

    builder.append("\n");
    builder.append((char)0);

    return builder.toString();
}

After some refactoring I end up with the following test class:

@Test
public class Test_03_StompIT extends TestNGCitrusTestDesigner {

    @CitrusTest
    public void testJoiningAndLeaving() {
        variable("username", "citrus:randomString(10, UPPERCASE)");

        echo("Connecting to server with STOMP");
        send("chatWebSocketClient")
                .payload(StompFrame.toWireFormat(new StompFrameBuilder()
                        .withCommand("CONNECT")
                        .withHeader("accept-version", "1.1")
                        .withHeader("heart-beat", "0,0")
                        .build()));

        echo("Verify connection is successful");
        receive("chatWebSocketClient")
                .messageType(MessageType.PLAINTEXT)
                .payload(StompFrame.toWireFormat(new StompFrameBuilder()
                        .withCommand("CONNECTED")
                        .withHeader("version", "1.1")
                        .withHeader("heart-beat", "0,0")
                        .build()));

        echo("Subscribe to destination: /topic/users/joined");
        send("chatWebSocketClient")
                .payload(StompFrame.toWireFormat(new StompFrameBuilder()
                        .withCommand("SUBSCRIBE")
                        .withHeader("id", "sub-0")
                        .withHeader("destination", "/topic/users/joined")
                        .build()));

        echo("Joining with user: ${username}");
        send("chatRestClient")
                .payload("")
                .http()
                .method(HttpMethod.POST)
                .path("/users/${username}");
        receive("chatRestClient")
                .messageType(MessageType.JSON)
                .http()
                .status(HttpStatus.OK);

        echo("Verify user-joined notification is received");
        receive("chatWebSocketClient")
                .messageType(MessageType.PLAINTEXT)
                .validationCallback(new AbstractValidationCallback<String>() {
                    @Override
                    public void validate(String payload, Map<String, Object> headers, TestContext context) {
                        Assert.assertTrue(payload.contains("MESSAGE\n"));
                        Assert.assertTrue(payload.contains("destination:/topic/users/joined\n"));
                        Assert.assertTrue(payload.contains("subscription:sub-0\n"));
                        Assert.assertTrue(payload.contains(context.getVariable("username")));
                    }
                });
    }
}

I’m not quite there yet but at least it’s slightly easier on the eye. I’m still validating the MESSAGE frame using partial string comparisons, so I’m going to clean that up as well now.

To do that I first need to add a second static method to the StompFrame class to convert the wire format into a StompFrame.

public static StompFrame fromWireFormat(String payload) {
    StompFrame stompFrame = new StompFrame();

    int separateAt = payload.indexOf("\n\n");
    String header = payload.substring(0, separateAt);
    String body = payload.substring(separateAt + 1);

    StringTokenizer stringTokenizer = new StringTokenizer(header, "\n");

    // set command
    stompFrame.setCommand(stringTokenizer.nextToken());

    // add headers
    while (stringTokenizer.hasMoreTokens()) {
        String token = stringTokenizer.nextToken();
        if (StringUtils.hasText(token) && token.contains(Character.toString(':'))) {
            String[] keyValue = token.split(":");
            stompFrame.addHeader(keyValue[0], keyValue[1]);
        }
    }

    // add body
    stompFrame.setBody(stripNullCharacter(body));

    return stompFrame;
}

Now I can finally refactor the validation callback:

echo("Verify user-joined notification is received");
receive("chatWebSocketClient")
        .messageType(MessageType.PLAINTEXT)
        .validationCallback(new AbstractValidationCallback<String>() {
            @Override
            public void validate(String payload, Map<String, Object> headers, TestContext context) {
                StompFrame stompFrame = StompFrame.fromWireFormat(payload);
                Assert.assertEquals(stompFrame.getCommand(), "MESSAGE");
                Assert.assertEquals(stompFrame.getHeader("destination"), "/topic/users/joined");
                Assert.assertEquals(stompFrame.getHeader("subscription"), "sub-0");
                String expectedUsername = String.format("\"%s\"", context.getVariable("username"));
                Assert.assertEquals(stompFrame.getBody(), expectedUsername);
            }
        });

I’m done.

Wrap-up

I added one final test class Test_04_CompleteIT which simulates all REST and STOMP communication in one single test including

  • subscribing to all STOMP topics,
  • joining the chat,
  • creating a room,
  • sending a message,
  • deleting a room
  • and finally leaving the chat

You can find it in my git repository along with all the other tests.

I hope I have inspired you in this blog to at least consider Citrus when you’re thinking about integration tests in the next or even current project. Although I concentrated mainly on testing a web application here, you can use Citrus for just about all integration test scenarios you can imagine. It’s used a lot in middleware applications, which is one of the reasons the test framework was developed to begin with, but it’s not limited to middleware as is hopefully demonstrated above.

Arquillian & Citrus in combination - Part 2

Some time has passed since part one of this blog post series and we have made some improvements on the Citrus Arquillian extension. So we can look forward to this post as we move on with a more complex test scenario where we include some Citrus mail server within our test. In part one we have already combined both frameworks Arquillian and Citrus with a basic example.

Let me recap first what we have so far. Citrus is part of the Arquillian test execution and we are able to call our employee REST JEE service which is deployed in an embedded Wildfly application server. Arquillian takes care on the test deployment and provides resources such as the endpoint URL to call. In our first test we called the employee service adding new employees and getting the complete list of all employees via REST.

Now we want to extend the employee service so that each new employee gets a welcome email message. Therefore we extend the employee JEE EJB with a mail session bean that is able to send email messages.

@Singleton
public class EmployeeRepository {

   private final Employees employees;

   @EJB
   private MailSessionBean mailSessionBean;

   public EmployeeRepository() {
       employees = new Employees();
   }

   public void addEmployee(Employee e) {
       employees.getEmployees().add(e);

       if (e.getEmail() != null && e.getEmail().length() > 0) {
         mailSessionBean.sendMail(e.getEmail(), "Welcome new employee",
           String.format("We welcome you '%s' to our company - now get to work!", e.getName()));
       }
   }

   public Employees getEmployees() {
       return employees;
   }
}

The employee service calls a mail session bean implementation when the email field is set on the new employee. The mail message is sent to the employee mail address as recipient and is a basic text message with subject and text body part. Lets have a closer look at the mail session bean implementation that uses Java mail API for creating sending out the mail mime message.

@Singleton
public class MailSessionBean {

    /** Logger */
    private static Logger log = LoggerFactory.getLogger(MailSessionBean.class);

    private int port = 2222;
    private String host = "localhost";
    private String from = "employee-registry@example.com";
    private String username = "employee-registry@example.com";
    private String password = "secretpw";

    public void sendMail(String to, String subject, String body) {
        Properties props = new Properties();
        props.put("mail.smtp.host", host);
        props.put("mail.smtp.port", port);
        props.put("mail.smtp.auth", true);

        Authenticator authenticator = new Authenticator() {
            private PasswordAuthentication pa = new PasswordAuthentication(username, password);
            @Override
            public PasswordAuthentication getPasswordAuthentication() {
                return pa;
            }
        };

        Session session = Session.getInstance(props, authenticator);
        session.setDebug(true);
        MimeMessage message = new MimeMessage(session);
        try {
            message.setFrom(new InternetAddress(from));
            InternetAddress[] address = {new InternetAddress(to)};
            message.setRecipients(Message.RecipientType.TO, address);
            message.setSubject(subject);
            message.setSentDate(new Date());
            message.setText(body);
            Transport.send(message);
        } catch (MessagingException e) {
            log.error("Failed to send welcome mail!", e);
        }
    }
}

Now in our test scenario we need a valid SMTP mail server on host localhost and port 2222. Fortunately Citrus is able to provide such a mail server so we add the citrus-mail Maven module to our test project.

<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-mail</artifactId>
  <version>2.3</version>
</dependency>

Now we are ready to add the mail server as a Citrus component. Citrus is working with Spring so we need to add the mail server to the Spring application context. We add a new property to the Arquillian descriptor so the Citrus extension will load our new configuration:

<extension qualifier="citrus">
  <property name="citrusVersion">2.3</property>
  <property name="autoPackage">true</property>
  <property name="suiteName">citrus-arquillian-suite</property>
  <property name="configurationClass">com.consol.citrus.samples.javaee.config.CitrusConfig</property>
</extension>

The configuration class is a Spring Java configuration class in our project that adds the new mail server as Spring bean to the application context.

@Configuration
public class CitrusConfig extends CitrusBaseConfig {

    private MailServer mailServer;

    @Bean
    public MailServer mailServer() {
        if (mailServer == null) {
            mailServer = new MailServer();
            mailServer.setPort(2222);
            mailServer.setAutoStart(true);
        }

        return mailServer;
    }
}

The configuration class must extend the Citrus base configuration class. This basic class adds all needed Spring beans for Citrus such as message validators, functions, validation matchers and so on. Important is our new mail server that uses the port 2222 and automatically starts with the Citrus application context. This is everything we need to do in order to add the mail server component to the Citrus runtime. We can add other Citrus endpoint components such as JMS endpoints, SOAP WebService servers and Camel endpoints here the same way. For now we are finished with the configuration and we can reference the mail server in our test case.

@RunWith(Arquillian.class)
@RunAsClient
public class EmployeeMailTest {

    @CitrusFramework
    private Citrus citrusFramework;

    @ArquillianResource
    private URL baseUri;

    private String serviceUri;

    @CitrusEndpoint
    private MailServer mailServer;

    @Deployment(testable = false)
    public static WebArchive createDeployment() {
        return ShrinkWrap.create(WebArchive.class)
            .addClasses(
                RegistryApplication.class, MailSessionBean.class, EmployeeResource.class,
                Employees.class, Employee.class, EmployeeRepository.class);
    }

    @Before
    public void setUp() throws Exception {
        serviceUri = new URL(baseUri, "registry/employee").toExternalForm();
    }

    @Test
    @CitrusTest
    public void testPostWithWelcomeEmail(@CitrusResource TestDesigner citrus) {
        citrus.send(serviceUri)
            .fork(true)
            .message(new HttpMessage("name=Rajesh&age=20&email=rajesh@example.com")
                    .method(HttpMethod.POST)
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED));

        citrus.receive(mailServer)
            .payload("<mail-message xmlns=\"http://www.citrusframework.org/schema/mail/message\">" +
              "<from>employee-registry@example.com</from>" +
              "<to>rajesh@example.com</to>" +
              "<cc></cc>" +
              "<bcc></bcc>" +
              "<subject>Welcome new employee</subject>" +
              "<body>" +
                "<contentType>text/plain; charset=us-ascii</contentType>" +
                "<content>We welcome you 'Rajesh' to our company - now get to work!</content>" +
              "</body>" +
            "</mail-message>")
            .header(CitrusMailMessageHeaders.MAIL_SUBJECT, "Welcome new employee")
            .header(CitrusMailMessageHeaders.MAIL_FROM, "employee-registry@example.com")
            .header(CitrusMailMessageHeaders.MAIL_TO, "rajesh@example.com");

        citrus.receive(serviceUri)
            .message(new HttpMessage()
                .statusCode(HttpStatus.NO_CONTENT));

        citrus.send(serviceUri)
            .fork(true)
            .message(new HttpMessage()
                .method(HttpMethod.GET)
                .accept(MediaType.APPLICATION_XML));

        citrus.receive(serviceUri)
            .message(new HttpMessage("<employees>" +
                    "<employee>" +
                        "<age>20</age>" +
                        "<name>Rajesh</name>" +
                        "<email>rajesh@example.com</email>" +
                    "</employee>" +
                "</employees>")
                .statusCode(HttpStatus.OK));

        citrusFramework.run(citrus.getTestCase());
    }

}

The Arquillian test case is basically the same as before in our first part example. The test now uses a @CitrusEndpoint annotated mail server component. The Arquillian Citrus extension will automatically inject the mail server Spring bean that we have added to the Citrus configuration before. In case multiple components of the same type are available in the configuration you can use the @CitrusEndpoint annotation with a Spring bean name like @CitrusEndpoint(name=”myMailServer”). We receive the mail message right after the test has called the service interface with the new employee Rajesh and a valid email field rajesh@example.com. The mail server Citrus component is ready to be used in a receive test action. Citrus waits for the mail message to arrive and performs message validation with an expected mail message. Citrus automatically converts the mail mime message to a XML message representation. We can expect the mail message content much better using the XML syntax as we can use the powerful XML validation with XPath, ignore and validation matcher support.

In addition to that Citrus adds some special header values for explicit validation in the receive action. That completes the mail server test case. Citrus automatically start a SMTP server that receives the mail message. The mail is not sent to a real recipient we just want to validate the mail message content in our test. The mail server did automatically accept the authentication in default mode. We could also switch to advanced mode where we can also validate the authentication steps. For now we keep it simple and are happy to receive the mail message. Please note that we can mix the receive actions of different interfaces in Citrus very easy. Citrus acts both as client and server on our REST and mail interfaces in the test.

This should close the book for this post. You can review the example code on christophd@github. In a next post I will show you how we can execute the test as part of the test deployment on the application server. Then we have direct access to container managed resources such as JMS connection factories and queues. Once again, stay tuned!

Arquillian & Citrus in combination - Part 1

Citrus and Arquillian both refer to themselves as integration test frameworks. Following from that you might think these frameworks somehow ship the same package but this is not the case. In fact the frameworks work brilliant together when it comes to automate the integration testing of JEE applications.

Arquillian has a focus on simplifying the archive deployment with the ability to run integration test cases inside the boundaries of an application container. The tests are literally part of a server deployment. This approach brings great advantages such as accessing deployment and container resources within the test case. The application is deployed to a real application server (e.g. Tomcat, Wildfly) which is a good idea as we then also test our application server configuration and we load the application with all container managed resources such as database connections, JNDI resources and so on. Secondly the test case itself is able to directly use the container managed resources with injection in the Arquillian test case which is a great thing to do! It becomes very easy to invoke your services in a close to production nature then.

Citrus primarily has the objective to simplify the usage of different messaging transports in a test case. Citrus offers ready to use components for sending and receiving messages as a client or server and helps to design a message flow across multiple service calls.

In combination the two frameworks manage complex integration test scenarios that perform fully automated. And this is what we want to have at the end of a day - fully automated integration tests. The magic is possible since Arquillian offers a great extension mechanism and Citrus provides an Arquillian extension module. Once the extension is enabled in your Arquillian project you can use Citrus features and components within a your Arquillian test case.

Lets have a look at this with a small example:

<extension qualifier="citrus">
  <property name="citrusVersion">2.2</property>
  <property name="autoPackage">true</property>
  <property name="suiteName">citrus-arquillian-suite</property>
</extension>

The extension configuration is placed in the basic Arquillian descriptor called arquillian.xml. We use the citrus qualifier so the settings are automatically loaded with the Citrus extension.

For now the possible extension settings are:

  • citrusVersion: The explicit version of Citrus that should be used. Be sure to have the same library version available in your project (e.g. as Maven dependency). This property is optional. By default the extension just uses the latest stable version.
  • autoPackage: When true (default setting) the extension will automatically add Citrus libraries and all transitive dependencies to the test deployment. This automatically enables you to use the Citrus API inside the Arquillian test even when the test is executed inside the application container.
  • suiteName: This optional setting defines the name of the test suite that is used for the Citrus test run. When using before/after suite functionality in Citrus this setting might be of interest.
  • configurationClass: Full qualified Java class name of customized Citrus Spring bean configuration to use when loading the Citrus Spring application context. As a user you can define a custom Citrus configuration with this optional setting.

So we also need to add the Citrus Maven dependency to our project:

<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-arquillian</artifactId>
  <version>2.2</version>
</dependency>

The dependency automatically adds the required Citrus core dependencies so you are ready to work with Citrus framework components. In case you want to use special Citrus components like JMS, FTP, Mail or something like that you need to add these modules each separately to your Maven POM, too. For now we want to use Http clients and the Citrus Java DSL so lets add the dependencies for that.

<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-http</artifactId>
  <version>2.2</version>
</dependency>
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-java-dsl</artifactId>
  <version>2.2</version>
</dependency>

Now we are ready to integrate Citrus in a first Arquillian test case:

@RunWith(Arquillian.class)
@RunAsClient
public class EmployeeResourceTest {

    @CitrusFramework
    private Citrus citrusFramework;

    @ArquillianResource
    private URL baseUri;

    private String serviceUri;

    @Deployment
    public static WebArchive createDeployment() {
        return ShrinkWrap.create(WebArchive.class)
            .addClasses(RegistryApplication.class, EmployeeResource.class,
                Employees.class, Employee.class, EmployeeRepository.class);
    }

    @Before
    public void setUp() throws MalformedURLException {
        serviceUri = new URL(baseUri, "registry/employee").toExternalForm();
    }

    /**
    * Test adding new employees and getting list of all employees.
    */
    @Test
    public void testCreateEmployeeAndGet(@CitrusTest CitrusTestBuilder citrus) {
        citrus.send(serviceUri)
            .message(new HttpMessage("name=Penny&age=20")
            .method(HttpMethod.POST)
            .contentType(MediaType.APPLICATION_FORM_URLENCODED));

        citrus.receive(serviceUri)
            .message(new HttpMessage()
            .statusCode(HttpStatus.NO_CONTENT));

        citrus.send(serviceUri)
            .message(new HttpMessage()
            .method(HttpMethod.GET)
            .accept(MediaType.APPLICATION_XML));

        citrus.receive(serviceUri)
        .message(new HttpMessage("<employees>" +
            "<employee>" +
              "<age>20</age>" +
              "<name>Penny</name>" +
              "</employee>" +
            "</employees>")
        .statusCode(HttpStatus.OK));

        citrusFramework.run(citrus.build());
    }
}

As you can see this is a normal Arquillian JUnit test case. We are running the test in client mode with the @RunAsClient annotation. As the Citrus extension is active in background we are able to inject the framework instance with the @CitrusFramework annotation. The Citrus framework instance is automatically loaded and configured with all necessary settings in background when the Arquillian test starts. As usual we can build the deployment archive with our server resources that we need to test and we have access to @ArquillianResource annotated resources such as the REST service endpoint URI of our application.

The test method itself is provided with a @CitrusTest annotated method parameter that represents the Citrus execution test builder. This is a Java DSL representation of what Citrus has to offer when it comes to sending and receiving messages over various message transports. The Citrus Arquillian extension will automatically inject this method parameter so we can use it inside the test method block in order to define the Citrus test logic.

Basically we invoke the deployed REST service with Citrus using the Java DSL send and receive methods. Each receive operation in Citrus also triggers the message validation mechanism. This includes a syntax check in case of SOAP and XML messages with a WSDL or XSD given and a semantic check on received message body and header values. The tester is able to give an expected message template that is used as comparison template. Citrus is able to deeply walk through XML or JSON message payloads comparing elements, attributes, namespaces and values. In case the received message response does not match the given message template the Arquillian test case will fail with respective validation errors. For now lets keep it simple so we just expect some Http response status codes in the receive operations. The last receive operation expects a very simple XML message payload with the newly added employee data.

With the ability to send and receive messages via Http REST we simply invoke the deployed service endpoint and validate its outcome. With a sequence of send and receive operations in Citrus we can build complex message flows with multiple service endpoints involved.

That’s it we have successfully combined both frameworks. Arquillian helps us to deploy and run our application inside an application container and we have easy access to managed resources. Citrus is able to invoke the services via Http with powerful validation of the received response messages. This is just a very simple sample for now as I just wanted to show the basic setup. Based on this knowledge we can continue with a more complex test scenario. We will start to use Arquillian in container testing mode and we will start to use more complex Citrus server components in one of my next posts. Stay tuned!

Apache Camel Integration Testing - Part 3

In this post I will continue with the Apache Camel integration testing scenario that we have worked on in part one and part two of this series. This time we focus on exception handling in Camel routes. First of all let’s add exception handling to our Camel route.

<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="helloRoute">
    <from uri="direct:hello"/>
    <to uri="seda:sayHello" pattern="InOut"/>
    <onException>
      <exception>com.consol.citrus.exceptions.CitrusRuntimeException</exception>
      <to uri="seda:errors"/>
    </onException>
  </route>
</camelContext>

Camel supports exception handling on specific exception types. The onException route logic is executed when the defined exception type was raised during message processing. In the sample route we call a separate Seda endpoint seda:errors for further exception handling. The challenge for our test scenario is obvious. We need to force this error handling and we need to make sure that the Seda endpoint seda:errors has been called accordingly.

Let’s add the error handling endpoint to the Citrus Spring bean configuration.

<citrus-camel:sync-endpoint id="sedaErrorHandlingEndpoint"
                       camel-context="camelContext"
                       endpoint-uri="seda:errors"/>

The static endpoint definition is not mandatory as Citrus is also able to work with dynamic endpoints. In our case the dynamic endpoint for consuming messages on the seda:errors endpoint would simply be camel:sync:seda:errors. The decision which kind of endpoint someone should use depends on how much the endpoint could be reused throughout multiple test scenarios. Of course static endpoints are highly reusable in different test cases. On the downside we have to manage the additional configuration burden. In this post I will use the static endpoint that is injected to the test case via Spring’s autowiring mechanism. Let’s write the integration test.

package com.consol.citrus.hello;

import com.consol.citrus.dsl.TestNGCitrusTestBuilder;
import com.consol.citrus.dsl.annotations.CitrusTest;
import com.consol.citrus.endpoint.Endpoint;
import org.testng.annotations.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

/**
 * @author Christoph Deppisch
 */
@Test
public class SayHelloTest extends TestNGCitrusTestBuilder {

    @Autowired
    @Qualifier("directHelloEndpoint")
    private Endpoint directHelloEndpoint;

    @Autowired
    @Qualifier("sedaHelloEndpoint")
    private Endpoint sedaHelloEndpoint;

    @Autowired
    @Qualifier("sedaErrorHandlingEndpoint")
    private Endpoint sedaErrorHandlingEndpoint;

    @CitrusTest(name = "SayHello_Error_Test")
    public void sayHello_Error_Test() {
        send(directHelloEndpoint)
            .fork(true)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");

        receive(sedaHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");

        sleep(500L);

        send(sedaHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Something went wrong!")
            .header("citrus_camel_exchange_exception",
                    "com.consol.citrus.exceptions.CitrusRuntimeException")
            .header("citrus_camel_exchange_exception_message",
                    "Something went wrong!");

        receive(sedaErrorHandlingEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Something went wrong!")
            .header("CamelExceptionCaught",
                "com.consol.citrus.exceptions.CitrusRuntimeException: Something went wrong!");

        send(sedaErrorHandlingEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello after error!");

        receive(directHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello after error!");
    }
}

The magic happens when Citrus sends back a synchronous response on the seda:sayHello endpoint which is done right after the sleep action in our sample test. Instead of responding with a usual plain text message we add special header values citrus_camel_exchange_exception and citrus_camel_exchange_exception_message.

send(sedaHelloEndpoint)
    .messageType(MessageType.PLAINTEXT)
    .payload("Something went wrong!")
    .header("citrus_camel_exchange_exception",
            "com.consol.citrus.exceptions.CitrusRuntimeException")
    .header("citrus_camel_exchange_exception_message",
            "Something went wrong!");

These special Citrus headers instruct the Citrus Camel endpoint to raise an exception. We are able to specify the exception type as well as the exception message. As a result the Citrus endpoint raises the exception on the Camel exchange which should force the exception handling in our Camel route.

The route onException block in our example should send the error to the seda:errors endpoint. So let’s consume this message in a next test step.

receive(sedaErrorHandlingEndpoint)
    .messageType(MessageType.PLAINTEXT)
    .payload("Something went wrong!")
    .header("CamelExceptionCaught",
        "com.consol.citrus.exceptions.CitrusRuntimeException: Something went wrong!");

With this receive action on the error endpoint we intend to validate that the exception handling was done as expected. We are able to check the error message payload and in addition to that we have access to the Camel internal message headers that indicate the exception handling. Both message payload and message headers are compared to expected values in Citrus.

As a next test step we should provide a proper response message that is used as fallback response. The response is sent back as synchronous response on the seda:errors endpoint saying Hello after error!. The Camel onException block and in detail the default Camel error handler will use this message as final result of the route logic. So finally in our test we can receive the fallback response message as result of our initial direct direct:hello request.

This completes this simple test scenario. We raised an exception and forced the Camel route to perform proper exception handling. With the Citrus endpoints in duty we received the error message and provided a fallback response message which is used as final result of the Camel route.

Error handling in message based enterprise integration scenarios is complex. We need to deal with delivery timeouts, retry strategies and proper transaction handling. This post only showed the top of the iceberg but I hope you got the idea of how to set up automated integration tests with Apache Camel routes. The Citrus framework is focused on providing real message endpoints no matter of what kind or nature (Http, JMS, REST, SOAP, Ftp, XML, JSON, Seda and so on). What we get is automated integration testing with real messages being exchanged on different transports and endpoints.

Apache Camel Integration Testing - Part 2

In part one of this blog series we have used Citrus in combination with Apache Camel for setting up a complete integration test scenario. Remember we have interacted with our Camel route via JMS as client and via SOAP Http WebService as a server.

Now in the second part we want to interact with a Camel route using direct and Seda in memory message transports. First of all we need a Camel route to test.

<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="helloRoute">
    <from uri="direct:hello"/>
    <to uri="seda:sayHello" pattern="InOut"/>
  </route>
</camelContext>

The Camel route is obviously very simple. The route reads messages from a direct inbound endpoint and forwards all messages to a Seda in memory channel. The Seda communication is synchronous so the route waits for a synchronous response to arrive. Now when we would like to test this simple route we would have to provide the inbound messages to trigger the route and we would have to provide proper synchronous response messages on the Seda endpoint.

Lets set up a Citrus test case for this test scenario. We need a direct Camel route message endpoint that is able to call the Camel route. The Camel endpoints are available with a separate Citrus module. We need to add this library to our project as test scoped dependency if not already done so.

<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-camel</artifactId>
  <version>${citrus.version}</version>
  <scope>test</scope>
</dependency>

Now we can use the Citrus Camel endpoint components in the Spring bean application context that is part of the Citrus project.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:citrus-camel="http://www.citrusframework.org/schema/camel/config"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                     http://www.citrusframework.org/schema/camel/config http://www.citrusframework.org/schema/camel/config/citrus-camel-config.xsd">

  <camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
    <route id="helloRoute">
      <from uri="direct:hello"/>
      <to uri="seda:sayHello" pattern="InOut"/>
    </route>
  </camelContext>

  <citrus-camel:sync-endpoint id="directHelloEndpoint"
                           camel-context="camelContext"
                           endpoint-uri="direct:hello"/>

  <citrus-camel:sync-endpoint id="sedaHelloEndpoint"
                           camel-context="camelContext"
                           endpoint-uri="seda:sayHello"/>
</beans>

As you can see we have added two Citrus endpoint components both coming from the Citrus Camel module. The first component is interacting with the direct endpoint direct:hello and the second component is interacting with the Seda seda:sayHello endpoint. Both Citrus components use synchronous message communication so we are able to send and receive messages synchronously. Let’s move on with writing a test case.

package com.consol.citrus.hello;

import com.consol.citrus.dsl.TestNGCitrusTestBuilder;
import com.consol.citrus.dsl.annotations.CitrusTest;
import com.consol.citrus.endpoint.Endpoint;
import org.testng.annotations.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

/**
 * @author Christoph Deppisch
 */
@Test
public class SayHelloTest extends TestNGCitrusTestBuilder {

    @Autowired
    @Qualifier("directHelloEndpoint")
    private Endpoint directHelloEndpoint;

    @Autowired
    @Qualifier("sedaHelloEndpoint")
    private Endpoint sedaHelloEndpoint;

    @CitrusTest(name = "SayHello_Ok_Test")
    public void sayHello_Ok_Test() {
        send(directHelloEndpoint)
            .fork(true)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");

        receive(sedaHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");

        sleep(500L);

        send(sedaHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Camel!");

        receive(directHelloEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Camel!");
    }
}

The test is using Spring’s autowiring mechanism in order to inject the Citrus message endpoint components. We have four message interactions in our test. First of all we send a plain text message to the direct Camel route endpoint. The Camel route is triggered and according to the route logic the message is forwarded to the Seda endpoint. The second test action is a message receive action on this same Seda endpoint. So if the Camel route logic is working as expected we should be able to receive a message here. The receive test action also performs a validation on the message content received. As a tester we specify the expected message payload. Citrus as test framework compares this expectation to the message content actually arriving. When everything is matching as expected we continue with the test.

As the Seda endpoint is synchronous we can send back a response to the calling client. In our test this is done with a respective send message action that references the same Seda endpoint. Before we send back a plain text response message we add a sleep test action in order to simulate some hard work on the backend. As a next step the Camel route receives our simulated response message and immediately responds to the calling direct endpoint client to complete the route. This is our last step in the test case where we receive the very same response message on the direct endpoint as final message response.

Please do not get confused with this test setup. This scenario is purely constructed for demonstrating how Citrus interacts with Camel routes in terms of synchronous communication on both ends (consuming and producing). As the test performs all actions four messages are exchanged in between Citrus and our Camel route to test. If everything is working as expected all messages are completed and the test case is successful.

Someone might feel uncomfortable in defining the Citrus endpoint components for each Camel route endpoint in the Spring application context just to reference them inside the test case. This is where dynamic endpoints come in handy. Citrus is also able to create the endpoint at runtime. See how this looks like.

package com.consol.citrus.hello;

import com.consol.citrus.dsl.TestNGCitrusTestBuilder;
import com.consol.citrus.dsl.annotations.CitrusTest;
import com.consol.citrus.endpoint.Endpoint;
import org.testng.annotations.Test;

/**
 * @author Christoph Deppisch
 */
@Test
public class SayHelloTest extends TestNGCitrusTestBuilder {

    @CitrusTest(name = "SayHello_Ok_Test")
    public void sayHello_Ok_Test() {
        send("camel:sync:direct:hello")
            .fork(true)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Camel!");

        receive("camel:sync:seda:sayHello")
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Camel!");

        sleep(500L);

        send("camel:sync:seda:sayHello")
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");

        receive(camel:sync:direct:hello)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello Citrus!");
    }
}

With the dynamic endpoints we do not have to use any of the predefined endpoint components in the Citrus Spring application context. The test just creates the endpoints automatically at runtime. The result is exactly the same as before.

We have seen that Citrus is able to both send and receive message from an to route message endpoints in Apache Camel. This is a good way of testing Camel routes with simulation of route interface partners. In the next part I will add some error scenarios where the Seda endpoint component is forcing an exception that should be handled within our Camel route.

Apache Camel Integration Testing - Part 1

Apache Camel is a great mediation and routing framework that integrates with almost every enterprise messaging transport. In the past I have experienced Camel projects struggling with integration testing where the actual message interfaces to boundary applications are not tested properly in an automated way.

So in a series of posts I would like to talk about integration testing strategies for Apache Camel projects using the Citrus integration test framework.

  • Part 1: Setup the Citrus test project and interact with a sample Apache Camel project with JMS and SOAP WebService components
  • Part 2: Invoke Camel routes from Citrus test cases and validate outbound messages
  • Part 3: Test error situations with Camel exception handling

So lets have a sample Camel project that we would like to test. We need a simple Camel route like this:

<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <route id="newsRoute">
    <from uri="jms:queue:JMS.Queue.News"/>
    <to uri="log:com.consol.citrus.camel?level=INFO"/>
    <to uri="spring-ws:http://localhost:8080?soapAction=newsFeed"/>
  </route>
</camelContext>

The route consumes messages from a JMS destination called JMS.Queue.News, logs the message content and forwards the message content to a Http SOAP WebService using the Spring WS library. So we have two different messaging interfaces (JMS and SOAP WebService) to boundary systems in our sample.

Camel does provide very good test strategies for mocking these message transports. In a unit test you can mock the boundary JMS and SOAP interfaces with special mock components. This is great, but sometimes error prone for the following reasons. The JMS protocol provides several settings that are essential for the whole application behavior. For instance the JMS consumer may operate with concurrent consumers, connection pooling and transactional behavior. These settings are done on the Camel JMS component and on the JMS connection factory. In a mocked unit test these settings are not included as the test mock just does not process the real JMS message transports. No doubt these settings make a significant difference in production and have to be tested. In addition to that the SOAP WebService interface uses a WSDL and other SOAP specific settings like the soap action header. We could also add WS-Security and WS-Addressing headers here. In a mocked unit test these quite important interface characteristics are not tested over the wire. The actual SOAP message is not created and you are not able to verify the complete message contents as they are sent over the wire.

So the crucial weapon to avoid bugs related to untested transport settings is integration testing where the actual JMS message broker and a real SOAP WebService endpoint are involved in the test. And this is where Citrus comes in. In a Citrus test case the actual JMS consumer and producer settings do apply as well as a fully qualified WebService endpoint that receives the messages via Http message protocol. We use a real JMS message broker and Http server as it is done in production.

Lets setup a Citrus project that is able to interact with the sample Camel application route.

Citrus as integration test framework works best with Maven. You can setup a Citrus Maven project in minutes with this quick start tutorial. Once you have done this we have a Maven project with some sample Citrus test cases already in it. No we need to add the JMS and SOAP WebService configuration to the Citrus project. First of all let us add ActiveMQ as JMS message broker.

<dependency>
  <groupId>org.apache.activemq</groupId>
  <artifactId>activemq-broker</artifactId>
  <version>5.14.3</version>
</dependency>
<dependency>
  <groupId>org.apache.activemq</groupId>
  <artifactId>activemq-spring</artifactId>
  <version>5.14.3</version>
</dependency>
<dependency>
  <groupId>org.apache.xbean</groupId>
  <artifactId>xbean-spring</artifactId>
  <version>${xbean.version}</version>
</dependency>

We need to add the ActiveMQ Maven dependencies to our Citrus project. This is done in the Maven POM pom.xml file. Once we have this we can add the message broker to the Citrus configuration. We add a new Spring application context file citrus-activemq-context.xml in src/citrus/resources folder.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:amq="http://activemq.apache.org/schema/core"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                     http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

  <!-- Embedded ActiveMQ JMS broker -->
  <amq:broker useJmx="false" persistent="false">
    <amq:transportConnectors>
      <amq:transportConnector uri="tcp://localhost:61616" />
    </amq:transportConnectors>
  </amq:broker>

  <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616" />
  </bean>
</beans>

As next step we include this new Spring context file in the citrus-context.xml so both the ActiveMQ message broker and the JMS connection factory get loaded during startup. Just add a import statement to the citrus-context.xml file like this:

<import resource="classpath:citrus-activemq-context.xml"/>

Good! Now we are ready to connect with the JMS message transport. Let us add the Citrus JMS endpoints. You can do this also in the citrus-context.xml file:

<citrus-jms:endpoint id="newsJmsEndpoint"
                   destination-name="JMS.Queue.News"
                   timeout="5000"/>

That’s it! Now we can send and receive JMS messages on the JMS destination JMS.Queue.News. Ok so let’s write a new integration test! We create a new Java class in src/citrus/java

package com.consol.citrus.news;

import com.consol.citrus.dsl.TestNGCitrusTestBuilder;
import com.consol.citrus.dsl.annotations.CitrusTest;
import com.consol.citrus.ws.message.SoapMessageHeaders;
import org.testng.annotations.Test;

/**
 * @author Christoph Deppisch
 */
@Test
public class NewsFeedTest extends TestNGCitrusTestBuilder {
    
    @CitrusTest(name = "NewsFeed_Ok_Test")
    public void newsFeed_Ok_Test() {
        send("newsJmsEndpoint")
            .payload("<News>" +
                         "<Message>Citrus rocks!</Message>" +
                     "</News>");
        
        receive("newsSoapServer")
            .payload("<News>" +
                         "<Message>Citrus rocks!</Message>" +
                     "</News>")
            .header(SoapMessageHeaders.SOAP_ACTION, "newsFeed");
    }
}

This represents a Citrus Java test case. Notice that this is nothing but a normal Java unit test. I use TestNG as unit test framework. Others might prefer JUnit which is also possible. I think TestNG is much more powerful but this is another story. Also notice that we referenced the newsJmsEndpoint Citrus component in the first send operation. We could have used Spring autowire injection of the JmsEndpoint here, but we want to keep it simple for now. What we have right now is an integration test which actually sends the JMS message with some news content to the JMS queue destination and on the other side we receive the real SOAP message as a web server. But wait! We have not yet added the SOAP server configuration yet! Let’s do this in the citrus-context.xml configuration file.

<citrus-ws:server id="newsSoapServer"
               port="8080"
               auto-start="true"
               timeout="10000"/>

The server starts a fully qualified Http web server with the SOAP endpoint for receiving the news request. In the test case we can reference the server in a receive operation. That’s it! We are able to run the test. Of course we also need to start our Camel application. You can do this in another process with Maven or you deploy your Camel application to some application server. Citrus is interacting with the Camel route as a normal interface client just using the JMS endpoint. Once you run the integration test you will see how the messages are actualy sent over the wire and you will see Citrus receiving the actual SOAP request message:

Received SOAP request:
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header jmsmessageid="ID:localhost-50188-1416562129343-3:4:1:1:1"/>
<SOAP-ENV:Body>
<News>
<Message>Citrus rocks!</Message>
</News>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Also Citrus will put this received message content to validation with comparing the message body and header to the expected content given in the test case. Let’s recap. What we have done is a complete integration test where the Camel route interfaces are called with real message transport interaction. The ActiveMQ JMS message broker is real the SOAP WebService server is real. The message transport configuration is tested properly with message conversion and expected message content validation. The Camel application is loaded and deployed with the complete configuration and settings.

Citrus invokes the Camel route by sending a proper JMS message to the route endpoint. The Camel route processes the message and sends out the SOAP request message. In case the message content is not as expected in the Citrus receive operation or in case the SOAP message is not arriving at all the integration test fails. In this integration test we can simulate both client and server side interaction with our Camel application.

As the Citrus test case is nothing but a normal TestNG/JUnit test we can integrate the test in our Maven build lifecycle and continuous integration process. This completes our first part of how to do extended integration testing with Apache Camel projects. In my next part I will concentrate on how to interact with Apache Camel routes using direct and Seda in memory message transports.

Testing webMethods with Citrus Part II

In part I of this tutorial I introduced the basic concepts and benefits of Citrus as a test driver for ESB projects in general and webMethods in particular. In this second part I want to discuss some Citrus project setup options and provide a quickstart template project for Ant users.

Maven or Ant?

Basically, Citrus support both options – you are free in your choice of the build system to use to drive your Citrus tests. Typically a Maven setup is preferred since Maven reduces the necessary project setup clutter one has to face when using Ant. Check this quickstart if you want to let Maven create your project structure for you.

However, everything in the webMethods world is ant-based (like tooling, deployment etc.), at least this was the case during my experience with the 8.2 version of webMethods, let me know if things have changed meanwhile. So it might make pretty good sense to setup the Citrus project with Ant as well to reuse existing project knowledge. That’s what the rest of this blog entry is basically about.

Ant Setup

The Citrus homepage provides an awesome Ant quickstart. Everything you need is described there in detail, however, it is quite a tough job to collect all the necessary dependency jar’s manually. Since version 1.4, Citrus provides a maven assembly goal which creates a directory with all dependency jar’s which can be thrown into the lib folder of your newly created Citrus project. You can trigger this assembly by grabbing the Citrus sources and execute

mvn assembly:assembly -Passembly-antlibs

from the project root. Unfortunately I faced some issues on my Macbook with Maven version 3.0.5 so this way was blocked for me. Additionally, not everyone might have interest or possibilities to check out the sources or have maven installed and setup on his machine. That’s basically why I want to provide a template project to speed up Ant project setup.

Citrus Ant Template Project

For those who prefer the easiest way (like me), I have set up up a little Eclipse template project, where everything needed to run Citrus with Ant is already in place. It contains

  • The correct folder structure
  • All necessary Citrus (1.4) core libs and dependencies
  • A prepared citrus citrus-context.xml and log4j.xml
  • A template build.xml file

You can grab the template project on Github here , import it (Select Import –> General/Archive File in Eclipse/SAG Designer) and kick start developing integration tests for your webMethods project. Typically, you will work in Software AG Designer to develop your webMethods solution, luckily it is based on Eclipse and you can perfectly work with your Citrus test projects in the Java perspective of SAG Designer. In fact, the template project is created and exported from SAG Designer 9.5.

In the next part, I will enhance our empty project with a first test: a SOAP request/reply scenario with Citrus against a webMethods package.

Testing webMethods with Citrus Part I

Continuous integration is almost mainstream nowadays. Probably no one wants to argue against the value of having an all-embracing integration test suite in place, which is lightweight enough to be executed on each code change. In this blog series I want to show the interplay between Citrus, the integration test framework written and maintained by ConSol and a commonly used Enterprise Service Bus, the webMethods Integration Server.

Meet the participants

Citrus is a lightweight integration test framework specially suited for enterprise integration challenges. It supports a broad set of communication protocols and is able to simulate partner applications of any type to provide proper end-to-end testing in a sandbox environment.

Our system-under-test is Software AG’s webMethods Integration Server, which is an industry-proof ESB solving complex integration and B2B challenges in large-scale deployments around the globe.

Why Citrus?

On a closer look at the built-in support for automated testing, webMethods provides integrated tooling (namely the wMTestSuite), directly incorporated into the Software AG Designer to support developers with tests operating on the basic building blocks inside webMethods – the Flow service.

Comparing this tooling to traditional software development, this means there is a good framework for Unit/white box testing in place, but: wmTestSuite is not suited for automated tests from an integration/black box perspective. For such tasks, several commercial tools are available, but this is where Citrus with its support for automated tests on interface level comes into play as a smart and fully open source alternative.

What’s coming up?

Over the next parts of this series I want to share the basic steps in order to setup a Citrus project and develop automated tests for several webMethods integration scenarios, including SOAP request/reply, SOAP and JMS mocking as well as flat file integration. In the end I might also cover the topic BPMN processes on top of webMethods which can be tested with Citrus as well.

Stay tuned for the next upcoming part II: basic project setup

Validate Excel files in Citrus

Lately I had to deal with Excel files as REST Http service response. I came up with a pretty clever validation mechanism in Citrus that I would like to share with you. You can apply the Excel validator to your Citrus project, too. It is not very complicated as you will see in this post.

First of all lets have a closer look at the response message we get from REST Http service.

HTTP/1.1 200 OK
Content-Language: de-DE
Content-Type: application/vnd.ms-excel;charset=windows-1252
Pragma: private
Cache-Control: private, must-revalidate
Content-Disposition: attachment; filename="ReportData.xls"
Transfer-Encoding: chunked
Server: Jetty(7.2.2.v20101205)

-- binary content --

The response message states a Content-Type header set to application/vnd.ms-excel which indicates the Excel content to the browser. Furthermore the Content-Disposition header informs the browser that he should open the save as dialog to the user rather than displaying the content. Finally the Excel file is added as binary content with charset windows-1252.

So now we would like to receive this message in our Citrus test also being able to validate the Excel file content as well as the important header entries. This is how we can do it:

First of all we introduce a custom message validator which handles the binary Excel message content. We intend to create an Excel workbook object that we can pass into the Citrus test. The tester is then able to write Groovy validation code accessing the Excel workbook object. Heres the code for the custom Excel message validator:

public class ExcelMessageValidator extends GroovyScriptMessageValidator {

    /**
     * Default constructor using a customized groovy script template 
     * with POI workbook support.
     */
    public ExcelMessageValidator() {
        super(new ClassPathResource("de/buk/aso/test/validation/excel-script-validation.groovy"));
    }
    
    @Override
    public boolean supportsMessageType(String messageType) {
        return messageType.equalsIgnoreCase("ms-excel");
    }

}

The message validator is quite simple isn’t it. We extend GroovyScriptMessageValidator as we would like to write groovy validation code inside the test. The validator uses a custom script template (excel-script-validation.groovy) and supports message type ms-excel. Keep that in mind as we will use this later in our test case. For now we add this message validator to the Citrus Spring application context (citrus-context.xml).

<bean id="excelMessageValidator" class="com.consol.citrus.validation.ExcelMessageValidator"/>

Not much has happened though in this validator’s Java code. The magic is done inside the Groovy script template excel-script-validation.groovy that we have added to the classpath in our project. Lets have a look at this file:

import java.io.ByteArrayInputStream
import java.io.IOException
import com.consol.citrus.*
import com.consol.citrus.message.*
import com.consol.citrus.variable.*
import com.consol.citrus.context.TestContext
import com.consol.citrus.exceptions.CitrusRuntimeException
import com.consol.citrus.validation.script.GroovyScriptMessageValidator.ValidationScriptExecutor
import org.apache.poi.hssf.usermodel.HSSFWorkbook

public class ExcelValidationScript implements ValidationScriptExecutor {
    public void validate(Message receivedMessage, TestContext context) {
        Map<String, Object> headers = receivedMessage.copyHeaders()
        String payload = receivedMessage.getPayload(String.class)
        
        HSSFWorkbook workbook;

        try {
            workbook = new HSSFWorkbook(new ByteArrayInputStream(payload.getBytes()))
        } catch (IOException e) {
            throw new CitrusRuntimeException("Failed to create excel workbook", e)
        }
        
        @SCRIPTBODY@
    }
}

Ah, now we are getting more precisely! We introduce a new HSSFWorkbook (see also http://poi.apache.org/) with the message payload that we have just received. Please do not mind the Java-like Groovy programming style - I want to keep things easy for Java programmers in this post. However we now have the workbook object ready for validation code in our test which automatically comes in where @SCRIPTBODY@ placeholder is located. So before we continue to look at the test example we shortly add apache poi jar dependency to our project Maven pom.

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.7</version>
</dependency>

Finally let’s write the Citrus test with custom Excel file validation code in Groovy:

<testcase name="ExcelValidationITest">
  <actions>
     <send endpoint="httpRestEndpoint">
        <message><data></data></message>
           <header>
              <element name="citrus_endpoint_uri" 
                  value="http://localhost:8080/rest-api/report/excel"/>
              <element name="citrus_http_method" value="GET"/>
              <element name="Content-Type" value="text/html"/>
              <element name="Accept" value="application/vnd.ms-excel"/>
          </header>
      </send>
            
      <receive endpoint="httpRestEndpoint">
          <message type="ms-excel">
            <validate>
              <script type="groovy">
               // Header validation
               assert headers["citrus_http_status_code"].toString() == '200'
               assert headers["citrus_http_reason_phrase"] == 'OK'
               assert headers["Content-Type"].toString() == 
                    'application/vnd.ms-excel;charset=windows-1252'
               assert headers["Content-Disposition"] == 
                    'attachment; filename="ReportData.xls"'

               // Excel workbook validation
               assert workbook.getSheetAt(0).getSheetName() == "Report Data"
             </script>
           </validate>
         </message>
     </receive>
  </actions>
</testcase>

We can access the Excel workbook object like a charm. Also the important message headers are checked for expected values. Do not forget to define the message type ms-excel in the receive action. This ensures that the validation mechanism in Citrus uses our custom Excel message validator for preparing the apache poi workbook object in advance.

That’s it! We are now able to validate Excel files in Citrus! The same thing can easily be done with other MS office formats (doc), too. If I have time the next days I will zip that project code and add it as download for you. Have fun with it!

Use TestNG data providers in Citrus

TestNG provides brilliant support for test parameters and data providers. With some annotation magic you are able to pass parameter values to your test method and finally to your Citrus test logic. Actually the TestNG parameters are injected to the Citrus test as normal test variables. Lets put this to the test with a simple example:

public class DataProviderITest extends AbstractTestNGCitrusTest {
    
    @Parameters( { "message" } )
    @Test(dataProvider = "citrusDataProvider")
    public void dataProviderITest(ITestContext testContext) {
        executeTest(testContext);
    }
    
    @Override
    protected Object[][] getParameterValues() {
        return new Object[][] {
            { "Hello World!" },
            { "Hallo Welt!" },
            { "Hallo Citrus!" },
        };
    }
}

We have to use the Citrus data provider (citrusDataProvider) along with a named parameter annotation (message) on the test method. Just add the annotations to the Citrus test method as shown in the example above. Next thing we override the method getParameterValues() in order to provide the actual parameter values. As you can see we provide three static values for the “message” parameter.

Inside the Citrus test you can use the test variable $ as usual. TestNG and Citrus automatically take care on creating the variable with respective value from data provider. The test case is very simple and looks like follows:

<testcase name="DataProviderITest">
    <actions>
        <echo>
            <message>${message}</message>
        </echo>
    </actions>
</testcase>

As we have three static parameter values in our data provider the whole Citrus test is executed three times. Each time the data provider injects the respective test parameter and ${message} variable. The Citrus test report gives us the test results with all parameters.

 echo Hello World!
 echo Hallo Welt!
 echo Hallo Citrus!

CITRUS TEST RESULTS
 
DataProviderITest('Hello World!') .................................. SUCCESS
DataProviderITest('Hallo Welt!') ................................... SUCCESS
DataProviderITest('Hallo Citrus!') ................................. SUCCESS

Total number of tests: 3
Skipped:   0 (0.0%)
Failed:    0 (0.0%)
Success:   3 (100.0%)

We can also use multiple test parameters at a time with all values coming from data provider. How about reading the parameter values from external property files or database?! I use this feature a lot for preparing my Citrus tests with dynamic parameter values from external resources and I bet you will enjoy this feature as much as I do. For more information on TestNG data providers please also have a look at the official documentation (http://testng.org/doc).

Integrate your own validator into your Citrus project

Citrus includes a lot of convenient features which are only waiting for you to discover and use them. The other day I needed to validate a SoapAttachment. As you probably already know, a SoapAttachment is referenced by a href property in an Include tag like this:

&lt;Include href="..." xmlns="http://www.w3.org/2004/08/xop/include"/&gt; 

Validation is quite easy when you’re still mock testing your application because you have full control over what your mock response will look like.

So just add the following bean to your citrus-context.xml:

<bean id="soapAttachmentValidator">
  <property name="ignoreAllWhitespaces" value="true"/>
</bean>

And use it in your test file like so:

<send endpoint="facadeMessageSender">
  <message>
    <data>
      <![CDATA[...-your request-...]]>
    </data>
  </message>
</send>
<ws:receive endpoint="facadeResponseHandler">
  <message schema-validation="false">
    <data>
      <![CDATA[
        <test:MyTestRes xmlns:mail="http://www.testdomain.com/foo/bar/test">
          <test:MyStructure>
            <Include href="@ignore@" xmlns="http://www.w3.org/2004/08/xop/include"/>
          </test:MyStructure>
        </test:MyTestRes>
      ]]>
    </data>
    <ignore path="//test:MyTestRes/test:MyStructure/*/@href" />
  </message>
  <extract>
    <message variable="contentId" path="///test:MyTestRes/test:MyStructure/*/@href"/>
  </extract>
  <!-- Extract cid: (unfortunately the @ is masked by %40 and has to be replaced too) -->
  <ws:attachment content-id="citrus:concat(citrus:substringBefore(citrus:substringAfter('${contentId}', 'cid:'), '%40www.testdomain.com'), '@www.testdomain.com')" 
                 content-type="TEXT/CALENDAR; 
                 charset=us-ascii" 
                 validator="soapAttachmentValidator">
    <ws:data>
      <![CDATA[...-the expected response-...]]>
    </ws:data>
  </ws:attachment>
</ws:receive>

Things become a little more complicated as soon as you start to connect to real backend systems for integration testing. Especially when timestamps are involved. In my case the SoapAttachment contains a calendar invitation in vCal format which is basically a simple text based key/value format. I have to ignore the value for the key LAST-MODIFIED because it would be impossible to predict this timestamp and it’s not really relevant either. So what I really need is a way to compare the expected structure with the actual result line by line, a possibility to completely ignore a line and a way to only ignore the value but still compare the keys of some lines.

To solve this, the Citrus framework offers the possibility to define your own validators and include them into your test cases. Simply add a Java class which extends AbstractSoapAttachmentValidator to your test project and implement the validateAttachmentContent method. This will be your new validator and could look somewhat like the simple example I’ll add to the end of this blog entry.

Go back to your citrus-context.xml and add your newly created validator bean:

<bean id="lineByLineSoapAttachmentValidator" class="com.testproject.itest.validation.LineByLineWithIgnoreSoapAttachmentValidator"/>

Now you can use the validator in your test files:

<ws:attachment content-id="citrus:concat(citrus:substringBefore(citrus:substringAfter('${contentId}', 'cid:'), '%40www.testdomain.com'),  '@www.testdomain.com')" content-type="TEXT/CALENDAR; charset=us-ascii" validator="lineByLineSoapAttachmentValidator">
  <ws:data>
    <![CDATA[
BEGIN:VCALENDAR
PRODID:-//Company//Test Project//DE
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
ORGANIZER:mailto:${user2.mailaddress}
ATTENDEE:mailto:${user3.mailaddress}
CATEGORIES:Test Category
DESCRIPTION:Event Description
DTEND:20110101T120000Z
DTSTART:20110101T100000Z
LAST-MODIFIED:@ignore@
LOCATION:A cool location
PRIORITY:1
SUMMARY:The summary of my test event @ignore@
@ignore@
UID:${randomNum}
END:VEVENT
END:VCALENDAR
    ]]>
  </ws:data>
</ws:attachment>

So here’s the promised simple example for your lineByLineSoapAttachmentValidator:

public class LineByLineWithIgnoreSoapAttachmentValidator extends AbstractSoapAttachmentValidator {

  private static final String IGNORE = "@ignore@";
  private static final String LINE_DELIMITERS_R_N = "[\\r\\n]+";
  
  @Override
  protected void validateAttachmentContent(SoapAttachment receivedAttachment, SoapAttachment controlAttachment) {
    try {
      String control = controlAttachment.getContent().trim();
      String received = receivedAttachment.getContent().trim();
      String[] controlLines = control.split(LINE_DELIMITERS_R_N);
      String[] receivedLines = received.split(LINE_DELIMITERS_R_N);
      
      // Check number of properties
      if (controlLines.length != receivedLines.length) {
        throw new CitrusRuntimeException("Number of lines are not equal. Expected: " + controlLines.length + " Received: " + receivedLines.length);
      }
      
      // Now compare the properties
      for (int i = 0; i &lt; controlLines.length; i++) {
        String controlLine = controlLines[i].trim();
        String receivedLine = receivedLines[i].trim();
        
        if (StringUtils.endsWithIgnoreCase(controlLine, IGNORE)) {
          compareBeginningOfLines(controlLine, receivedLine);
          // value shall be ignored, so just continue;
          continue;
        }
        
        if (StringUtils.startsWithIgnoreCase(controlLine, IGNORE)) {
          compareEndingOfLines(controlLine, receivedLine);
          // value shall be ignored, so just continue;
          continue;
        }
        
        if (!controlLine.equalsIgnoreCase(receivedLine)) {
          throw new CitrusRuntimeException("Lines are not equal.\n\nExpected: " + controlLine + "\n\nReceived: " + receivedLine);
        }
      }
    } catch (Exception e) {
      throw new CitrusRuntimeException("Validation failed!", e);
    }
  }
  
  private void compareBeginningOfLines(String controlLine, String receivedLine) {
    // check the beginning of the line
    String begin = controlLine.substring(0, controlLine.length() - 8);
    
    if (StringUtils.hasText(begin) &amp;&amp; !StringUtils.startsWithIgnoreCase(receivedLine, begin)) {
      throw new CitrusRuntimeException("Beginning of lines not equal.\n\nExpected: " + controlLine + "\n\nReceived: " + receivedLine);
    }
  }
  
  private void compareEndingOfLines(String controlLine, String receivedLine) {
    // check the beginning of the line
    String end = controlLine.substring(8);
    
    if (StringUtils.hasText(end) &amp;&amp; !StringUtils.endsWithIgnoreCase(receivedLine, end)) {
      throw new CitrusRuntimeException("Ending of lines not equal.\n\nExpected: " + controlLine + "\n\nReceived: " + receivedLine);
    }
  }
}

Have fun using Citrus!

yvonne

Citrus and TestNG groups

TestNG groups add great flexibility to the Citrus test execution. We are able to divide all tests into several groups reaching a sophisticated seperation of concerns in our test setup. As an example I want to classify some of my functional Citrus tests as “long-running”. These tests may not apply to continuous execution every time I package my project. Instead of this I want to set up a scheduled integration build to execute those long-running tests in a time schedule.

We simply declare the TestNG groups in the Java part of the Citrus tests, like this:

@Test(groups = {"functional", "long-running"})
public void sampleCitrusITest(ITestContext testContext) {
    executeTest(testContext);
}

In Maven I can configure the TestNG groups to be included and excluded during build lifecycle. For better usability I add properties and profiles to my Maven POM, so the configuration looks like follows:

[...]
<properties>
    <!-- TestNG groups (functional, performance, long-running) -->
    <testGroups>functional, performance</testGroups>
    <testGroupsExcluded>long-running</testGroupsExcluded>
</properties>
[...]
<profiles>
    <!-- Several profiles activating single testng groups for execution -->
    <profile>
      <id>all-tests</id>
      <properties>
        <testGroups>functional, performance, long-running</testGroups>
        <testGroupsExcluded></testGroupsExcluded>
      </properties>
    </profile>
    <profile>
      <id>performance</id>
      <properties>
        <testGroups>performance</testGroups>
      </properties>
    </profile>
    <profile>
      <id>long-running</id>
      <properties>
        <testGroups>long-running</testGroups>
        <testGroupsExcluded></testGroupsExcluded>
      </properties>
    </profile>
</profiles>

With this setup I am not forced to wait for the long-running tests every time I build the project locally as these test group is excluded by default. However with Maven profiles I am able to run all TestNG groups together or explicitly execute a single group:

mvn install (runs only short-running tests, long-running tests are excluded)
mvn install -Pfunctional,long-running (runs all tests)
mvn install -Plong-running (runs only long-running test group)

TestNG groups in combination with Maven are extremely useful to break down Citrus tests into logical units. For additional reading and other ways to execute TestNG groups without using Maven please see the official TestNG documentation.

Handle SOAP-ENV:mustUnderstand headers

By setting the SOAP mustUnderstand header attribute to “1”, you indicate that the service provider must process the SOAP header entry. In case the service provider is not able to handle this special header a SOAP fault server error is sent back to the calling client. In this post I would like to point out an easy way to support these mustUnderstand headers when simulating SOAP WebServices with Citrus.

This sample SOAP request header contains some mustUnderstand header entries:

<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
  <com:UserID 
      xmlns:com="http://www.consol.com/soap-mustunderstand" 
      SOAP-ENV:mustUnderstand="1">123456789</com:UserID>
</SOAP-ENV-Header>

The service provider respectively the Citrus SOAP server simulation has to handle this UserID. Otherwise the client request can not succeed and is responded with SOAPFault.

Citrus uses the SpringWS project to provide SOAP WebServices endpoints for clients. In SpringWS you are able to add interceptors to the request processing chain in order to support SOAP-ENV:mustUnderstand headers. In this example we add the interceptors directly to the endpoint mapping in the Spring application context.

<!-- Special soap endpoint interceptor that accepts our must-understand headers -->
<bean id="soapMustUnderstandEndpointInterceptor" class="com.consol.ws.sample.SimpleMustUnderstandEndpointInterceptor"/>

We have added a custom SimpleMustUnderstandEndpointInterceptor. Let us have a closer look at the custom endpoint interceptor implementation:

public class SimpleMustUnderstandEndpointInterceptor implements SoapEndpointInterceptor {
    private final String SAMPLE_NS = "http://www.consol.com/soap-mustunderstand";
    
    @Override
    public boolean understands(SoapHeaderElement header) {
        if(header.getName().getNamespaceURI().equals(SAMPLE_NS) && 
            header.getName().getLocalPart().equals("UserID")) {
                return true;
        }
        
        return false;
    }
    
    [...]
}

SpringWS automatically raises SOAP server faults in case we do not handle a SOAP-ENV:mustUnderstand header in the interceptor chain. Fortunately we explicitly accept the UserID SOAP header in the interceptor implementation with the understands() method returning “true” in that case. With this SOAP endpoint interceptor we are able to support mustUnderstand headers in Citrus WebService endpoints.

Citrus also offers a default interceptor implementation which handles mustUnderstand headers with a simple configuration. Just add the interceptor to the request processing and you will not have to implement the interceptor on your own.

<!-- Special soap endpoint interceptor that accepts our must-understand headers -->
<bean id="soapMustUnderstandEndpointInterceptor" class="com.consol.citrus.ws.interceptor.SoapMustUnderstandEndpointInterceptor">
  <property name="acceptedHeaders">
     <list>
         <value>{http://www.consol.com/soap-mustunderstand}UserID</value>
     </list>
  </property>
</bean>

Customize meta information

Test cases in Citrus are usually provided with some meta information like the author’s name or the date of creation. This post shows how to extend on this to include your very specific meta data on your own.

Meta-data that comes shipped with Citrus looks like

<testcase name="PwdChange_Ok_1_Test">
    <meta-info>
        <author>Christoph</author>
        <creationdate>2010-01-18</creationdate>
        <status>FINAL</status>
        <last-updated-by>Christoph</last-updated-by>
        <last-updated-on>2010-01-18T15:00:00</last-updated-on>
    </meta-info>

    [...]
</testcase>

However there may be some additional data needed to meet your individual testing strategy. Therefore you can extend the meta-info section at the very beginning of a test case very easily. Let me use a simple example to show how it is done.

First of all we define our custom meta information elements in a XML schema:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"  
        xmlns:tns="http://www.citrusframework.org/samples/my-testcase-info" 
        targetNamespace="http://www.citrusframework.org/samples/my-testcase-info"
        elementFormDefault="qualified">

    <element name="requirement" type="string"/>
    <element name="pre-condition" type="string"/>
    <element name="result" type="string"/>
    <element name="classification" type="string"/>
</schema>

The schema declares four simple elements (requirement, pre-condition, result and classification) all typed as string. Later we want to add those elements as children to the meta-info element in the test case. But before we can do that let us add the new xsd schema to our project. As I use a Maven project layout the schema file goes to “src/main/resources/com/consol/citrus/schemas/my-testcase-info.xsd”.

Next thing we need to do is to announce the new schema to Spring. A Citrus test case is nothing else but a simple Spring configuration file with customized XML schema support. Therefore Spring needs to know our XML schema while parsing the test case configuration file. So we add the spring.schemas file to following location in our project: src/main/resources/META-INF/spring.schemas

The file maps virtual schema locations to the actual xsd locations in our project. The file content for our example will look like follows:

http\://www.citrusframework.org/samples/my-testcase-info/my-testcase-info.xsd=com/consol/citrus/schemas/my-testcase-info.xsd

So now we are finally ready to use the new meta-info elements inside the test case. Note: We use a separate namespace declaration with a custom namespace prefix “custom”.

<?xml version="1.0" encoding="UTF-8"?>
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
    xmlns:spring="http://www.springframework.org/schema/beans" 
    xmlns:custom="http://www.citrusframework.org/samples/my-testcase-info"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.citrusframework.org/schema/testcase 
http://www.citrusframework.org/schema/testcase/citrus-testcase.xsd 
http://www.citrusframework.org/samples/my-testcase-info 
http://www.citrusframework.org/samples/my-testcase-info/my-testcase-info.xsd">

    <testcase name="PwdChange_Ok_1_Test">
        <meta-info>
            <author>Christoph</author>
            <creationdate>2010-01-18</creationdate>
            <status>FINAL</status>
            <last-updated-by>Christoph</last-updated-by>
            <last-updated-on>2010-01-18T15:00:00</last-updated-on>
            <custom:requirement>REQ10001</custom:requirement>
            <custom:pre-condition>Existing user, sufficient rights</custom:pre-condition>
            <custom:result>Password reset in database</custom:result>
            <custom:classification>PasswordChange</custom:classification>
        </meta-info>

        [...]
    </testcase>
</spring:beans>

As you see it is quite easy to add custom meta information to your Citrus test case. The customized elements may be precious for automatic reporting. XSL transformations for instance are able to read those meta information elements in order to generate automatic test reports and documentation.

You can also declare our new XML schema in the Eclipse preferences section as user specific XML catalog entry. Then even the schema code completion in your Eclipse XML editor will be available for our customized meta-info elements.