Version: 3.4.0

citrus-logo

1. Preface

Integration tests are a critical part software testing. In contrast to unit tests where the primary goal is to verify a single class or method in isolation to other parts of the software the integration tests look at a wider scope with several components and software parts interacting with each other.

Integration tests often rely on infrastructure components such as I/O, databases, 3rd party services and so on. In combination with messaging and multiple message transports as client and/or server the automated tests become a tough challenge. Testers need sufficient tool support to master this challenge for automated integration test. Citrus as an Open Source framework is here to help you master this challenge.

In a typical enterprise application and middleware scenario automated integration testing of message-based interfaces is exhausting and sometimes barely possible. Usually the tester is forced to simulate several interface partners in an end-to-end integration test.

The first thing that comes to ones mind is manual testing. No doubt manual testing is fast. In a long term perspective manual testing is time-consuming and causes severe problems regarding maintainability as they are error prone and not repeatable.

Citrus provides a complete test automation tool for integration testing of message-based enterprise applications. You can test your message interfaces (Http REST, SOAP, JMS, Kafka, TCP/IP, FTP, …​) to other applications as client and server. Every time a code change is made all automated Citrus tests ensure the stability of software interfaces and its message communication.

Regression testing and continuous integration is very easy as Citrus fits into your build lifecycle (Maven or Gradle) as usual Java unit test (JUnit, TestNG, Cucumber).

With powerful validation capabilities for various message formats like XML, CSV or JSON Citrus is ready to provide fully automated integration tests for end-to-end use cases. Citrus effectively composes complex messaging use cases with response generation, error simulation, database interaction and more.

This documentation provides a reference guide to all features of the Citrus test framework. It gives a detailed picture of effective integration testing with automated integration test environments. Since this document is open, please do not hesitate to give feedback in form of comments, change requests, fixes and pull requests. We are more than happy to continuously improve this documentation with your help!

2. Introduction

Citrus provides automated integration tests for message-based enterprise applications. The framework is Open Source and supports various messaging transports (Http REST, SOAP, JMS, Kafka, TCP/IP, FTP, …​) and data formats (XML, Json, plaintext, binary).

The Citrus tests use well-known unit test frameworks (JUnit, TestNG, Cucumber) for execution and integrates with build tools like Maven or Gradle. In addition, Citrus leverages standard libraries like Spring Framework and Apache Camel.

2.1. Overview

Citrus supports simulating interface partners across different messaging transports. You can easily produce and consume messages with a wide range of protocols like HTTP, JMS, TCP/IP, FTP, SMTP and more. The framework is able to act both as a client and server. In each communication step Citrus is able to validate message contents towards syntax and semantics.

In addition to that the Citrus framework offers a wide range of test actions to take control of the process flow during a test (e.g. iterations, system availability checks, database connectivity, parallelism, delays, error simulation, scripting and many more).

The test is able to describe a whole use case scenario including several interface partners that exchange many messages with each other. The composition of complex message flows in a single test case with several test steps is one of the major features in Citrus.

You can choose how to describe the test case definition either with pure XML or a Java domain specific language. The tests can be executed multiple times as automated integration test.

With JUnit and TestNG integration Citrus can easily be integrated into your build lifecycle process (Maven or Gradle). During a test Citrus simulates all surrounding interface partners (client or server) without any coding effort. With easy definition of expected message content (header and body) for XML, CSV, SOAP, JSON or plaintext messages Citrus is able to validate the incoming data towards syntax and semantics.

2.2. Usage scenarios

Citrus should help you whenever it comes to verify a message-based software with its interfaces to other components and partners using automated integration tests. Every project that interacts with other components over messaging transports needs to simulate these interface partners on the client or server side in a test scenario. Citrus is here to help you master these test automation tasks.

usage_sample.jpg

This test set up is typical for a Citrus use case. In such a test scenario we have a system under test (SUT) with several messaging interfaces to other applications. A client application invokes services on the SUT and triggers business logic. The SUT is linked to several backend applications over various messaging transports (here SOAP, JMS, and Http). As part of the business logic one or more of these backend services is called and interim message notifications and responses are sent back to the client application.

This generates a bunch of messages that are exchanged throughout the components involved.

In the automated integration test Citrus needs to send and receive those messages over different transports. Citrus takes care of all interface partners (ClientApplication, Backend1, Backend2, Backend3) and simulates their behavior by sending proper response messages in order to keep the message flow alive.

Each communication step comes with message validation and comparison against an expected message template (e.g. XML or JSON data). In addition to messaging steps Citrus is also able to perform arbitrary other test actions (e.g. perform a database query between requests).

In fact a Citrus test case is nothing but a normal JUnit or TestNG test case. This makes it very straight forward to run the tests from your favorite Java IDE (Eclipse, IntelliJ, VSCode, …​) and as part of your software build process (Maven or Gradle). The Citrus test become repeatable and give you fully automated integration tests to ensure software quality and interface stability.

The following reference guide walks through all Citrus capabilities and shows how to have a great integration test experience.

3. Setup

This chapter discusses how to get started with Citrus. It deals with the installation and set up of the framework, so you are ready to start writing test cases after reading this chapter.

Usually you add Citrus as a test-scoped dependency library in your project. Build tools like Maven or Gradle provide standard integration for test libraries such as Citrus. As Citrus tests are nothing but normal unit tests (JUnit, TestNG, Cucumber) you can run the tests with the standard unit test build integration (e.g. via maven-failsafe plugin).

This chapter describes the Citrus project setup possibilities, choose one of them that fits best to include Citrus into your project.

3.1. Using Maven

Citrus uses Maven internally as a project build tool and provides extended support for Maven projects. Maven will ease up your life as it manages project dependencies and provides extended build life cycles and conventions for compiling, testing, packaging and installing your Java project.

In case you already use Maven in your project you can just add Citrus as a test-scoped dependency.

As Maven handles all project dependencies automatically you do not need to download any Citrus project artifacts in advance. If you are new to Maven please refer to the official Maven documentation and find out how to set up a Maven project.

Assuming you have a proper Maven project setup you can integrate Citrus with it. Just add the Citrus project dependencies in your Maven pom.xml as a dependency like follows.

  • We add Citrus as test-scoped project dependency to the project POM (pom.xml)

Add Citrus base dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-base</artifactId>
  <version>${citrus.version}</version>
  <scope>test</scope>
</dependency>
  • The dependency above adds the base functionality of Citrus. You need to add modules as you require them.

Add modules as required in your project. For instance Http support
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-http</artifactId>
  <version>${citrus.version}</version>
  <scope>test</scope>
</dependency>
Choose test runtime (JUnit, TestNG, Cucumber) that is used to run the tests.
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-testng</artifactId>
  <version>${citrus.version}</version>
  <scope>test</scope>
</dependency>
  • Citrus integrates nicely with the Spring framework. In case you want to use the Spring dependency injection and bean configuration capabilities just add the Spring support in Citrus.

Add Spring support
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-spring</artifactId>
  <version>${citrus.version}</version>
  <scope>test</scope>
</dependency>
  • Also, Citrus provides a Maven plugin that you can add. The plugin provides some convenience functionalities such as creating new tests from command line.

Add Citrus Maven plugin
<plugin>
  <groupId>com.consol.citrus.mvn</groupId>
  <artifactId>citrus-maven-plugin</artifactId>
  <version>${citrus.version}</version>
  <configuration>
    <author>Donald Duck</author>
    <targetPackage>com.consol.citrus</targetPackage>
  </configuration>
</plugin>

The Maven project is now ready to use Citrus. You can start writing new test cases with the Citrus Maven plugin:

Create new test
mvn citrus:create-test

The command above starts an interactive command line interface that helps you to create a test.

Once you have written the Citrus test cases you can execute them automatically in your Maven software build lifecycle. The tests will be included into your projects integration-test phase using the Maven failsafe plugin. Here is a sample failsafe configuration for Citrus.

Maven failsafe plugin
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${maven.failsafe.version}</version>
  <executions>
    <execution>
      <id>integration-tests</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
 </plugin>

The Citrus test sources go to the default Maven test source directory src/test/java and src/test/resources:

You are now ready to call the usual Maven verify goal (mvn verify) in order to build your project and run the tests. The Citrus integration tests are executed automatically during the build process.

Run all tests with Maven
mvn verify
Run single test by its name
mvn verify -Dit.test=MyFirstCitrusIT
The Maven failsafe plugin by default executed tests with specific name pattern. This is because integration tests should not execute in Maven unit test phase, too. Your integration tests should follow the failsafe name pattern with each test name beginning or ending with 'IT'.
If you need additional assistance in setting up a Citrus Maven project please visit our Maven setup tutorial on https://citrusframework.org/tutorials.html.

3.2. Using Gradle

As Citrus tests are nothing but normal JUnit or TestNG tests the integration to Gradle as build tool is as easy as adding the source files to a folder in your project. With the Gradle task execution for integration tests you are able to execute the Citrus tests like you would do with normal unit tests.

The Gradle build configuration goees to the build.gradle and settings.gradle files. The files define the project name and the project version.

Gradle project configuration
rootProject.name = 'citrus-sample-gradle'
group 'com.consol.citrus.samples'
version '${citrus.version}'

The Citrus libraries are available on Maven central repository. This means you should add this repository so Gradle knows how to download the required Citrus artifacts.

Add Maven central repository
repositories {
    mavenCentral()
}

Citrus stable release versions are available on Maven central. If you want to use the very latest next version as snapshot preview you need to add the ConSol Labs snapshot repository which is optional. Now lets move on with adding the Citrus libraries to the project.

Add Citrus test scoped dependencies
dependencies {
    testCompile group: 'com.consol.citrus', name: 'citrus-base', version: '${citrus.version}'
    testCompile group: 'com.consol.citrus', name: 'citrus-http', version: '${citrus.version}'
    testCompile group: 'org.testng', name: 'testng', version: '6.11'
    [...]
}

Citrus provides various modules that encapsulate different functionalities. The citrus-base module is the basis and holds core functionality. In addition, you may add further modules that match your project needs (e.g. add Http support with citrus-http).

As a runtime the project chose to use TestNG. You can also use JUnit or Cucumber as a test runtime. Each of those frameworks integrates seamlessly with the Gradle build.

Choose test runtime provider
test {
    useTestNG()
}

Of course JUnit is also supported. This completes the Gradle build configuration settings. You can move on to writing some Citrus integration tests and add those to src/test/java directory.

You can use the Gradle wrapper for compile, package and test the sample with Gradle build command line.

Run the build with Gradle
 gradlew clean build

This executes all Citrus test cases during the build. You will be able to see Citrus performing some integration test logging output.

If you just want to execute all tests you can call:

Run all tests
gradlew clean check

Of course, you can also run the Citrus tests from your favorite Java IDE. Just start the Citrus test as a normal unit test using the Gradle integration in IntelliJ, Eclipse or VSCode.

4. Runtimes

A Citrus test case is nothing but Java unit test leveraging well-known standard tools such as JUnit, TestNG or Cucumber as a runtime.

Chances are very high that Java developers are familiar with at least one of the standard tools. Everything you can do with JUnit and TestNG you can do with Citrus tests as well (e.g. Maven build integration, run tests from your favorite IDE, include tests into a continuous build tool).

Why is Citrus related to unit test frameworks although it represents a framework for integration testing? The answer to this question is quite simple: This is because Citrus wants to benefit from standard libraries such as JUnit and TestNG for Java test execution. Both unit testing frameworks offer various ways of execution and are widely supported by other tools (e.g. continuous build, build lifecycle, development IDE).

You can write the Citrus test code in a Java domain specific language or in form of an XML test declaration file that gets loaded as part of the test. The Java domain specific language in Citrus is a set of classes and methods to leverage the test code in form of a fluent API. Users can simply configure a test action with the different options using a fluent builder pattern style DSL.

Citrus Java DSL
@CitrusTest(name = "Hello_IT")
public void helloTest() {
    given(
        variable("user", "Citrus")
    );

    then(
        echo().message("Hello ${user}!"
    ));
}

The sample above is a very simple Citrus test that creates a test variable and prints a message to the console. The Java DSL you write is the same for all runtimes (JUnit, TestNG, Cucumber, etc.) and should help you to also solve very complex test scenarios.

The following sections have a closer look at the different runtimes for Citrus.

4.1. TestNG

TestNG stands for next generation testing and has had a great influence in adding Java annotations to the unit test community. Citrus is able to define tests as executable TestNG Java classes.

The TestNG support is shipped in a separate Maven module. You need to include the module as a dependency in your project.
TestNG module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-testng</artifactId>
  <version>${citrus.version}</version>
</dependency>

4.1.1. TestNG tests

See the following sample showing how to write a Citrus test on top of TestNG:

TestNG Citrus test
package com.consol.citrus.samples;

import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.testng.TestNGCitrusSupport;

@Test
public class Simple_IT extends TestNGCitrusSupport {

    @CitrusTest(name = "Simple_IT")
    public void simpleTest() {
        description("First example showing the basic Java DSL!");

        given(
            variable("user", "Citrus")
        );

        then(
            echo().message("Hello ${user}!"
        ));
    }
}

If you are familiar with TestNG you will see that the Java class is a normal TestNG test class using the usual @Test annotation. For convenience reasons you can extend a basic Citrus TestNG base class TestNGCitrusSupport which enables the Citrus test execution as well as the Java DSL features for us.

You can also combine Citrus with the Spring framework and its dependency injection and IoC capabilities. In order to enable Spring support in Citrus add the citrus-spring module to your project and extend TestNGCitrusSpringSupport as a base class. With the Spring support in Citrus the test is able to use @Autowired annotations for injecting Spring beans into the test class and you can define the Spring application context with @Configuration annotations for instance.

In addition, the test methods use the @CitrusTest annotation which allows setting properties such as test names and packages.

The Citrus test logic goes directly as the method body with using the Citrus Java domain specific language features. As you can see the Java DSL is able to follow BDD (Behavior Drive Design) principles with Given-When-Then syntax. As an alternative to that you can just use run() for all test actions.

Pure test action DSL
@CitrusTest(name = "Simple_IT")
public void simpleTest() {
    description("First example showing the basic Java DSL!");

    run(variable("user", "Citrus"));

    run(
        echo().message("Hello ${user}!"
    ));
}

The great news is that you can still use the awesome TestNG features in with the Citrus test class (e.g. parallel test execution, test groups, setup and tear down operations and so on). Just to give an example we can simply add a test group to our test like this:

Set test groups
@Test(groups = {"long-running"})
public void longRunningTest() {
    ...
}

For more information on TestNG please visit the official TestNG website, where you find a complete reference documentation. The following sections deal with a subset of these TestNG features in particular.

4.1.2. Use TestNG data providers

TestNG as a framework comes with lots of great features such as data providers. Data providers execute a test case several times with different test data. Each test execution works with a specific parameter value. You can use data provider parameter values as test variables in Citrus. See the next listing on how to use TestNG data providers in Citrus:

TestNG Citrus data provider test
public class DataProviderIT extends TestNGCitrusSupport {

    @CitrusTest
    @CitrusParameters( {"message", "delay"} )
    @Test(dataProvider = "messageDataProvider")
    public void dataProvider(String message, Long sleep) {
        run(echo(message));
        run(sleep().milliseconds(sleep));

        run(echo("${message}"));
        run(echo("${delay}"));
    }

    @DataProvider
    public Object[][] messageDataProvider() {
        return new Object[][] {
                { "Hello World!", 300L },
                { "Citrus rocks!", 1000L },
                { "Hi from Citrus!", 500L },
        };
    }
}

Above test case method is annotated with TestNG data provider called messageDataProvider . In the same class you can write the data provider that returns a list of parameter values. TestNG will execute the test case several times according to the provided parameter list. Each execution is shipped with the respective parameter value.

According to the @CitrusParameter annotation the test will have test variables called message and delay.

4.1.3. Run tests in parallel

Integration tests tend to be more time-consuming compared to pure unit tests when it comes to execute tests. This is because integration tests often need to initialize test infrastructure (e.g. test servers, database connections). Running tests in parallel can reduce the overall test suite time a lot.

When running tests in parallel you need to make sure each test operates on its own set of resources. Tests must not share components such as the Citrus Java DSL test action runner or the test context.

You should be using the resource injection to make sure each test operates on its own resources.

Resource injection
public class ResourceInjection_IT extends TestNGCitrusSupport {

    @Test
    @CitrusTest
    public void injectResources(@Optional @CitrusResource TestCaseRunner runner,
                                @Optional @CitrusResource TestContext context) {

        runner.given(
            createVariable("random", "citrus:randomNumber(10)")
        );

        runner.run(
            echo("The random number is: ${random}")
        );
    }
}

First of all the method parameters must be annotated with @Optional because the values are not injected by TestNG itself but by the Citrus base test class. Finally, the parameter requires the @CitrusResource annotations in order to mark the parameter for Citrus resource injection.

Now each method uses its own resource instances which makes sure that parallel test execution can take place without having the risk of side effects on other tests running at the same time. Of course, you also need to make sure that the message exchange in your tests is ready to be performed in parallel (e.g. use message selectors).

4.2. JUnit5

With JUnit version 5 the famous unit test framework offers a new major version. The JUnit platform provides awesome extension points for other frameworks like Citrus to integrate with the unit testing execution.

Citrus provides extensions in order to enable Citrus related dependency injection and parameter resolving in your JUnit5 test.

The JUnit5 support is shipped in a separate Maven module. You need to include the module as a dependency in your project.
JUnit5 module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-junit5</artifactId>
  <version>${citrus.version}</version>
</dependency>

4.2.1. Citrus extension

You can use the Citrus JUnit5 extension on your test as follows:

JUnit5 Citrus test
package com.consol.citrus.samples;

import com.consol.citrus.GherkinTestActionRunner;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.junit.jupiter.CitrusSupport;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@CitrusSupport
public class Simple_IT {

    @Test
    @CitrusTest(name = "Simple_IT")
    public void simpleTest(@CitrusResource GherkinTestActionRunner runner) {
        runner.description("First example showing the basic Java DSL!");

        runner.given(
            variable("user", "Citrus")
        );

        runner.then(
            echo().message("Hello ${user}!"
        ));
    }
}

The class above is using the JUnit5 @Test annotation as a normal unit test would do. The @CitrusSupport annotation marks the test to use the Citrus JUnit5 extension. This enables us to use the @CitrusTest annotation on the test and adds support for the parameter injection for the TestActionRunner.

You can use the @CitrusSupport annotation, or you can use the classic @ExtendWith(CitrusExtension.class) annotation to enable the Citrus support for JUnit5.

The Citrus Java DSL runner is the entrance to the Java fluent API provided by Citrus. The sample above uses the Gherkin test runner variation for leveraging the BDD (Behavior Driven Development) style Given-When-Then syntax.

You can also inject the current TestContext in order to get access to the current test variables used by Citrus.

You can also combine Citrus with the Spring framework and its dependency injection and IoC capabilities. In order to enable Spring support in Citrus add the citrus-spring module to your project and use the @ExtendsWith(CitrusSpringExtension.class) annotation. With the Spring support in Citrus the test is able to load components via the Spring application context.

4.2.2. Endpoint injection

In addition to injecting test resources you can also inject endpoints via @CitrusEndpoint annotated field injection in your test class. This enabled you to inject endpoint components that are defined in the Citrus context configuration.

JUnit5 Citrus endpoint injection
package com.consol.citrus.samples;

import com.consol.citrus.annotations.*;
import com.consol.citrus.GherkinTestActionRunner;
import com.consol.citrus.junit.jupiter.CitrusSupport;
import com.consol.citrus.http.client.HttpClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.http.HttpStatus;

@CitrusSupport
public class Simple_IT {

    @CitrusEndpoint
    private HttpClient httpClient;

    @Test
    @CitrusTest
    public void test(@CitrusResource GherkinTestActionRunner runner) {
        runner.http().client(httpClient)
                    .send()
                    .get("/hello");

        runner.http().client(httpClient)
                    .receive()
                    .response(HttpStatus.OK);
    }
}

4.2.3. Citrus Spring extension

Spring is a famous dependency injection framework that also provides support for JUnit5. Citrus is able to load its components as Spring beans in an application context. The Citrus JUnit5 extension works great with the Spring extension.

The Spring extension loads the application context and Citrus adds all components to the Spring bean configuration.

JUnit5 Citrus Spring test
@CitrusSpringSupport
@ContextConfiguration(classes = CitrusSpringConfig.class)
public class SpringBean_IT {

    @Autowired
    private DirectEndpoint direct;

    @Test
    @CitrusTest
    void springBeanTest(@CitrusResource TestActionRunner actions) {
        actions.$(send().endpoint(direct)
                    .message()
                    .body("Hello from Citrus!"));

        actions.$(receive().endpoint(direct)
                    .message()
                    .body("Hello from Citrus!"));
    }
}

The test now uses the @CitrusSpringSupport annotation which combines the @ExtendsWith(CitrusSpringExtension.class) and @ExtendsWith(SpringExtension.class) annotation. This way the test combines the Spring application context management with the Citrus Java DSL functionality.

You can load Spring beans with @Autowired into your test. Also you can use the @CitrusResource annotations to inject the test action runner fluent Java API.

The Spring application context should use the basic CitrusSpringConfig configuration class to load all Citrus components as Spring beans. You can customize the Spring application context by adding more configuration classes.

4.3. JUnit4

JUnit4 is still very popular and widely supported by many tools even though there is a new major version with JUnit5 already available. In general Citrus supports both JUnit4 and JUnit5 as test execution framework.

The JUnit4 support is shipped in a separate Maven module. You need to include the module as a dependency in your project.
JUnit4 module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-junit</artifactId>
  <version>${citrus.version}</version>
</dependency>

4.3.1. JUnit4 tests

See the following sample test class that uses JUnit4.

JUnit4 Citrus test
package com.consol.citrus.samples;

import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.junit.JUnit4CitrusSupport;

@Test
public class Simple_IT extends JUnit4CitrusSupport {

    @CitrusTest(name = "Simple_IT")
    public void simpleTest() {
        description("First example showing the basic Java DSL!");

        given(
            variable("user", "Citrus")
        );

        then(
            echo().message("Hello ${user}!"
        ));
    }
}

The simple test class above uses the normal @Test annotation and extends the base class JUnit4CitrusSupport. This is the most convenient way to access the Citrus Java DSL capabilities. As an alternative you may switch to using the CitrusJUnit4Runner in your test class.

The fine thing here is that we are still able to use all JUnit features such as before/after hooks or ignoring tests.

After the test run the result is reported exactly like a usual JUnit unit test would do. This also means that you can execute this Citrus JUnit class like every other JUnit test, especially out of any Java IDE, with Maven, with Gradle and so on.

You can also combine Citrus with the Spring framework and its dependency injection and IoC capabilities. In order to enable Spring support in Citrus add the citrus-spring module to your project and extend JUnit4CitrusSpringSupport as a base class. With the Spring support in Citrus the test is able to use @Autowired annotations for injecting Spring beans into the test class and you can define the Spring application context with @Configuration annotations for instance.

4.3.2. Run tests in parallel

Integration tests tend to be more time-consuming compared to pure unit tests when it comes to execute tests. This is because integration tests often need to initialize test infrastructure (e.g. test servers, database connections). Running tests in parallel can reduce the overall test suite time a lot.

When running tests in parallel you need to make sure each test operates on its own set of resources. Tests must not share components such as the Citrus Java DSL test action runner or the test context.

You should be using the resource injection to make sure each test operates on its own resources.

Resource injection
public class ResourceInjection_IT extends JUnit4CitrusSupport {

    @Test
    @CitrusTest
    public void injectResources(@CitrusResource TestCaseRunner runner,
                                @CitrusResource TestContext context) {

        runner.given(
            createVariable("random", "citrus:randomNumber(10)")
        );

        runner.run(
            echo("The random number is: ${random}")
        );
    }
}

The method parameters require the @CitrusResource annotations in order to mark the parameter for Citrus resource injection.

Now each method uses its own resource instances which makes sure that parallel test execution can take place without having the risk of side effects on other tests running at the same time. Of course, you also need to make sure that the message exchange in your tests is ready to be performed in parallel (e.g. use message selectors).

4.4. Cucumber

Behavior driven development (BDD) is a very popular concept when it comes to find a common understanding of test scopes test logic. The idea of defining and describing the software behavior as basis for all tests in prior to translating those feature descriptions into executable tests is a very interesting approach because it includes the technical experts as well as the domain experts.

With BDD the domain experts should be able to read and verify tests and the technical experts get a detailed description of what should happen in the test.

The test scenario descriptions follow the Gherkin syntax with a "Given-When-Then" structure. The Gherkin language is business readable and helps to explain business logic with help of concrete examples.

There are several frameworks in the Java community supporting BDD concepts. Citrus has dedicated support for the Cucumber framework because Cucumber is well suited for extensions and plugins. So with the Citrus and Cucumber integration you can write Gherkin syntax scenarios in order to run those as Citrus integration tests.

The Cucumber components in Citrus are located in a separate Maven module. You need to include the module as a Maven dependency to your project.
Cucumber module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-cucumber</artifactId>
  <version>${citrus.version}</version>
</dependency>

Cucumber works with both JUnit and TestNG as unit testing framework. You can choose which framework to use with Cucumber. So following from that we need a Maven dependency for the unit testing framework support:

Cucumber JUnit support
<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-junit</artifactId>
  <version>${cucumber.version}</version>
</dependency>

In order to enable Citrus Cucumber support we need to specify a special object factory in the environment. The most comfortable way to specify a custom object factory is to add this property to the cucumber.properties in classpath.

cucumber.properties
cucumber.object-factory=com.consol.citrus.cucumber.backend.CitrusObjectFactory

This special object factory takes care on creating all step definition instances. The object factory is able to inject @CitrusResource annotated fields in step classes. We will see this later on in the examples. The usage of this special object factory is mandatory in order to combine Citrus and Cucumber capabilities.

The CitrusObjectFactory will automatically initialize the Citrus world for us. This includes the default Citrus context configuration that is automatically loaded within the object factory. So you can define and use Citrus components as usual within your test.

After these preparation steps you are able to combine Citrus and Cucumber in your project.

In case you want to use Spring support in Citrus with a Spring application context you should use the following factory implementation.
cucumber.properties
cucumber.object-factory=com.consol.citrus.cucumber.backend.spring.CitrusSpringObjectFactory

4.4.1. Cucumber options

Cucumber is able to run tests with JUnit. The basic test case is an empty test which uses the respective JUnit runner implementation from cucumber.

MyFeature.java
@RunWith(Cucumber.class)
@CucumberOptions(
  plugin = { "pretty", "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}

The test case above uses the Cucumber JUnit test runner. In addition to that we give some options to the Cucumber execution. In case you want to have the usual Citrus test results reported you can add the special Citrus reporter implementation com.consol.citrus.cucumber.CitrusReporter. This class is responsible for printing the Citrus test summary. This reporter extends the default Cucumber reporter so the default Cucumber report summary is also printed to the console.

That completes the JUnit class configuration. Now we are able to add feature stories and step definitions to the package of our test MyFeatureIT . Cucumber and Citrus will automatically pick up step definitions and glue code in that test package. So lets write a feature story echo.feature right next to the MyFeatureIT test class.

echo.feature
Feature: Echo service

  Scenario: Say hello
    Given My name is Citrus
    When I say hello to the service
    Then the service should return: "Hello, my name is Citrus!"

  Scenario: Say goodbye
    Given My name is Citrus
    When I say goodbye to the service
    Then the service should return: "Goodbye from Citrus!"

As you can see this story defines two scenarios with the Gherkin Given-When-Then syntax. Now we need to add step definitions that glue the story description to Citrus test actions. Lets do this in a new class EchoSteps .

EchoSteps.java
public class EchoSteps {

    @CitrusResource
    private TestCaseRunner runner;

    @Given("^My name is (.*)$")
    public void my_name_is(String name) {
        runner.variable("username", name);
    }

    @When("^I say hello.*$")
    public void say_hello() {
        runner.when(
                send("echoEndpoint")
                    .message()
                    .type(MessageType.PLAINTEXT)
                    .body("Hello, my name is ${username}!"));
    }

    @When("^I say goodbye.*$")
    public void say_goodbye() {
        runner.when(
                send("echoEndpoint")
                    .message()
                    .type(MessageType.PLAINTEXT)
                    .body("Goodbye from ${username}!"));
    }

    @Then("^the service should return: \"([^\"]*)\"$")
    public void verify_return(final String body) {
        runner.then(
                receive("echoEndpoint")
                    .message()
                    .type(MessageType.PLAINTEXT)
                    .body("You just said: " + body));
    }

}

The step definition class is a normal POJO that uses a some annotations such as @CitrusResource annotated TestCaseRunner. The Citrus backend injects the test runner instance at runtime.

The step definition contains normal @Given, @When or @Then annotated methods that match the scenario descriptions in our feature file. Cucumber will automatically find matching methods and execute them. The methods add test actions to the test runner as we used to do in normal Java DSL tests.

That is a first combination of Citrus and Cucumber BDD. The feature file gets translated into step implementations that use Citrus test action runner Java API to run integration tests with behavior driven development.

4.4.2. Cucumber XML steps

The previous section handled glue code in Java in in form of step definitions accessing the Java test runner fluent API. This chapter deals with the same concept with just XML configuration.

Citrus provides a separate configuration namespace and schema definition for Cucumber related step definitions. Include this namespace into your Spring configuration in order to use the Citrus Cucumber configuration elements.

Spring bean configuration schema
<spring:beans xmlns:spring="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://www.citrusframework.org/schema/cucumber/testcase"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.citrusframework.org/schema/cucumber/testcase
     http://www.citrusframework.org/schema/cucumber/testcase/citrus-cucumber-testcase.xsd">

    [...]

</spring:beans>

The JUnit Cucumber feature class itself does not change. We still use the Cucumber JUnit runner implementation with some options specific to Citrus:

MyFeatureIT.java
@RunWith(Cucumber.class)
@CucumberOptions(
    plugin = { "pretty", "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}

The feature file with its Gherkin scenarios does also not change:

echo.feature
Feature: Echo service

  Scenario: Say hello
    Given My name is Citrus
    When I say hello to the service
    Then the service should return: "Hello, my name is Citrus!"

  Scenario: Say goodbye
    Given My name is Citrus
    When I say goodbye to the service
    Then the service should return: "Goodbye from Citrus!"

In the feature package my.company.features we add a new XML file EchoSteps.xml that holds the new XML step definitions:

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

  <step given="^My name is (.*)$" parameter-names="username">
    <citrus:create-variables>
      <citrus:variable name="username" value="${username}"/>
    </citrus:create-variables>
  </step>

  <step when="^I say hello.*$">
    <citrus:send endpoint="echoEndpoint">
      <citrus:message type="plaintext">
        <citrus:data>Hello, my name is ${username}!</citrus:data>
      </citrus:message>
    </citrus:send>
  </step>

  <step when="^I say goodbye.*$">
    <citrus:send endpoint="echoEndpoint">
      <citrus:message type="plaintext">
        <citrus:data>Goodbye from ${username}!</citrus:data>
      </citrus:message>
    </citrus:send>
  </step>

  <step then="^the service should return: &quot;([^&quot;]*)&quot;$" parameter-names="body">
    <citrus:receive endpoint="echoEndpoint">
      <citrus:message type="plaintext">
        <citrus:data>You just said: ${body}</citrus:data>
      </citrus:message>
    </citrus:receive>
  </step>

</spring:beans>

The above step definition uses pure XML actions. Citrus will automatically read the step definition and add those to the Cucumber runtime. Following from that the step definitions are executed when matching a statement in the feature story.

The XML step files follow a naming convention. Citrus will look for all files located in the feature package with name pattern **/**.Steps.xml and load those definitions when Cucumber starts up.

The XML steps are able to receive parameters from the Gherkin regexp matcher. The parameters are passed to the step as test variable. The parameter names get declared in the optional attribute parameter-names. In the step definitions you can use the parameter names as test variables.

The test variables are visible in all upcoming steps, too. This is because the test variables are global by default. If you need to set local state for a step definition you can use another attribute global-context and set it to false in the step definition. This way all test variables and parameters are only visible in the step definition. Other steps will not see the test variables.
Another notable thing is the XML escaping of reserved characters in the pattern definition. You can see that in the last step where the then attribute is escaping quotation characters.
Escape reserved characters
<step then="^the service should return: &quot;([^&quot;]*)&quot;$" parameter-names="body">
...
</step>

We have to do this because otherwise the quotation characters will interfere with the XML syntax in the attribute.

This completes the description of how to add XML step definitions to the cucumber BDD tests.

4.4.3. Cucumber Spring support

Cucumber provides support for Spring dependency injection in step definition classes. The Cucumber Spring capabilities are included in a separate module. So we first of all we have to add this dependency to our project:

<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-spring</artifactId>
  <version>${cucumber.version}</version>
</dependency>

The Citrus Cucumber extension has to handle things different when Cucumber Spring support is enabled. Therefore we use another object factory implementation that also support Cucumber Spring features. Change the object factory property in cucumber.properties to the following:

cucumber.properties
cucumber.object-factory=com.consol.citrus.cucumber.backend.spring.CitrusSpringObjectFactory

Now we are ready to add @Autowired Spring bean dependency injection to step definition classes:

EchoSteps.java
@ContextConfiguration(classes = CitrusSpringConfig.class)
public class EchoSteps {
    @Autowired
    private Endpoint echoEndpoint;

    @CitrusResource
    protected TestDesigner designer;

    @Given("^My name is (.*)$")
    public void my_name_is(String name) {
        designer.variable("username", name);
    }

    @When("^I say hello.*$")
    public void say_hello() {
        designer.send(echoEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Hello, my name is ${username}!");
    }

    @When("^I say goodbye.*$")
    public void say_goodbye() {
        designer.send(echoEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("Goodbye from ${username}!");
    }

    @Then("^the service should return: \"([^\"]*)\"$")
    public void verify_return(final String body) {
        designer.receive(echoEndpoint)
            .messageType(MessageType.PLAINTEXT)
            .payload("You just said: " + body);
    }
}

As you can see we used Spring autowiring mechanism for the echoEndpoint field in the step definition. Also be sure to define the @ContextConfiguration annotation on the step definition. The Cucumber Spring support loads the Spring application context and takes care on dependency injection. We use the Citrus CitrusSpringConfig Java configuration because this is the main entrance for Citrus test cases. You can add custom beans and further Spring related configuration to this Spring application context. If you want to add more beans for autowiring do so in the Citrus Spring configuration. Usually this is the default citrus-context.xml which is automatically loaded.

Of course, you can also use a custom Java Spring configuration class here. Please be sure to always import the Citrus Spring Java configuration classes, too.

As usual, we are able to use @CitrusResource annotated TestCaseRunner fields for building the Citrus integration test logic. With this extension you can use the full Spring testing power in your tests in particular dependency injection and also transaction management for data persistence tests.

4.4.4. YAKS step definitions

YAKS is a side project of Citrus and provides some predefined steps for typical integration test scenarios that you can use out-of-the-box.

You can basically define send/receive operations and many other predefined steps to handle Citrus test actions. As these steps are predefined in YAKS you just need to use them in your feature stories. The step definitions with glue to test actions is handled automatically in YAKS.

If you want to enable predefined steps support in your test you need to include the YAKS module as a Maven dependency.

YAKS module dependency
<dependency>
  <groupId>org.citrusframework.yaks</groupId>
  <artifactId>yaks-standard</artifactId>
  <version>${yaks.version}</version>
  <scope>test</scope>
</dependency>

After that you need to include the glue code package in your test class like this:

Include YAKS steps
@RunWith(Cucumber.class)
@CucumberOptions(
    extraGlue = { "org.citrusframework.yaks.standard" },
    plugin = { "pretty", "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {

}

Instead of writing the glue code on our own in step definition classes we include the glue package org.citrusframework.yaks.standard as extra glue. This automatically loads all YAKS step definitions in this module. Once you have done this you can use predefined steps without having to write any glue code in Java.

The YAKS framework provides the following modules with predefined steps:

Table 1. YAKS modules
Module Description

yaks-standard

Standard steps such as test variables, sleep/delay, log/print, …​

yaks-http

Http steps for client and server side communication

yaks-openapi

Load Open API specifications and invoke/verify operations with generated test data

yaks-kubernetes

Manage Kubernetes resources (e.g. pods, deployments, custom resources)

yaks-knative

Steps to connect with Knative eventing and messaging

yaks-jms

Send/receive steps via JMS queues/topics

yaks-kafka

Steps to publish/subscribe on Kafka messaging

yaks-jdbc

Steps to connect to relational databases

yaks-camel

Steps to access Apache Camel components and Camel routes

yaks-camel-k

Manage Camel-K resources on Kubernetes

yaks-selenium

Run UI tests with Selenium using Selenium grid or standalone containers

yaks-groovy

Leverage Groovy scripts as Citrus endpoint and component configuration

Once again it should be said that the step definitions included in this modules can be used out-of-the-box. You can start to write feature stories in Gherkin syntax that trigger the predefined steps.

4.5. Main CLI runtime

Citrus provides a main class that you can run from command line.

The Main CLI support is shipped in a separate Maven module. You need to include the module as a dependency in your project.
Main CLI module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-main</artifactId>
  <version>${citrus.version}</version>
</dependency>
Run Citrus main

5. Running tests in Java

The Citrus test holds a sequence of test actions. Each action represents a very special purpose such as sending or receiving a message.

Despite the fact that message exchange is one of the main actions in an integration test framework for message-based applications Citrus is more than just that. Each test case in Citrus is able to perform various actions such as connecting to the database, transforming data, adding iterations and conditional steps.

With the default Citrus actions provided out of the box users can accomplish very complex use cases in integration testing. In Citrus you can configure and add test actions in Java using a test action runner API that leverages a fluent builder pattern API.

5.1. Test action runner

The test action runner is the entry to the fluent Java API. You can configure test actions with a fluent builder style API and immediately run the actions with the runner.

See the following example to see the Java domain specific language in action.

Test action runner
import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.dsl.testng.TestNGCitrusSupport;

@Test
public class Service_IT extends TestNGCitrusSupport {

    @CitrusTest(name = "Service_IT")
    public void serviceTest() {
        run(echo("Before service call"));

        run(echo("After service call"));
    }
}

The test action runner executes each test action immediately as you use the provided run() method. All actions provided in Citrus represent a certain functionality (e.g. send/receive messages, delay the test, access a database). Citrus ships with a wide range of test actions, but you are also able to write your own test actions and execute them during a test.

By default, all actions run sequentially in the same order as they are defined in the test case. In case one single action fails the whole test case is failing. Of course, you can leverage parallel action execution with the usage of test containers.

The TestNGCitrusSupport and JUnit4CitrusSupport base classes are not thread safe by default. This is simply because the base class is holding state to the current test action runner instance in order to delegate method calls to this instance. Parallel test execution is not available with this approach. Fortunately there is a way to support parallel test execution through resource injection. Read more about this in JUnit4 or TestNG support.

5.2. Gherkin test action runner

The test action runner is also available as Gherkin style runner with given(), when(), then() methods. The Gherkin test action runner follows the Behavior Driven Development concepts of structuring the test into the three parts: Given a certain context, when an event occurs, then an outcome should be verified.

Gherkin test action runner
@Test
public class Service_IT extends TestNGCitrusSupport {

    @CitrusTest(name = "Service_IT")
    public void serviceTest() {
        given(
            echo("Setup the context")
        );

        when(
            echo("Trigger the event")
        );

        then(
            echo("Verify the outcome")
        );
    }
}

5.3. Test meta information

The user is able to provide some additional information about the test case. The meta-info section at the very beginning of the test case holds information like author, status or creation date.

Test meta information
@CitrusTest
public void sampleTest() {
    description("This is a Test");
    author("Christoph");
    status(Status.FINAL);

    run(echo("Hello Citrus!"));
}

The status allows the following values:

  • DRAFT

  • READY_FOR_REVIEW

  • DISABLED

  • FINAL

This information gives the reader first impression about the test and is also used to generate test documentation. By default, Citrus is able to generate test reports in HTML and Excel in order to list all tests with their metadata information and description.

Tests with the status DISABLED will not be executed during a test suite run. So someone can just start adding planned test cases that are not finished yet in status DRAFT. In case a test is not runnable yet because it is not finished, someone may disable a test temporarily to avoid causing failures during a test run.

The test description should give a short introduction to the intended use case scenario that will be tested. The user should get a short summary of what the test case is trying to verify.

5.4. Finally block

Java developers might be familiar with the concept of try-catch-finally blocks. The finally section contains a list of test actions that will be executed guaranteed at the very end of the test case even if errors did occur during the execution before.

This is the right place to tidy up things that were previously created by the test like cleaning up the database for instance.

Finally block
@CitrusTest
public void sampleTest() {
    given(
        doFinally()
            .actions(echo("Do finally - regardless of any error before"))
    );

    echo("Hello Test Framework");
}

As an example imagine that you have prepared some data inside the database at the beginning of the test and you need to make sure the data is cleaned up at the end of the test case.

Finally block example
@CitrusTest
public void finallyBlockTest() {
    variable("orderId", "citrus:randomNumber(5)");
    variable("date", "citrus:currentDate('dd.MM.yyyy')");

    given(
        doFinally()
            .actions(sql(dataSource).statement("DELETE FROM ORDERS WHERE ORDER_ID='${orderId}'"))
    );

    when(
        sql(dataSource).statement("INSERT INTO ORDERS VALUES (${orderId}, 1, 1, '${date}')")
    );

    then(
        echo("ORDER creation time: citrus:currentDate('dd.MM.yyyy')")
    );
}

In the example the first action creates an entry in the database using an INSERT statement. To be sure that the entry in the database is deleted after the test, the finally section contains the respective DELETE statement that is always executed regardless the test case state (successful or failed).

The finally section must be placed at the very beginning of the test. This is because the test action runner is immediately executing each test action as it is called within the Java DSL methods. This is the only way the test case can perform the final actions also in case of previous error.

A finally block placed at the very end of the test will not take action unless put in a traditional Java try-finally-block:

Traditional try-finally block
@CitrusTest
public void finallyBlockTest() {
    variable("orderId", "citrus:randomNumber(5)");
    variable("date", "citrus:currentDate('dd.MM.yyyy')");

    try {
        when(
            sql(dataSource).statement("INSERT INTO ORDERS VALUES (${orderId}, 1, 1, '${date}')")
        );

        then(
            echo("ORDER creation time: citrus:currentDate('dd.MM.yyyy')")
        );
    } finally {
        then(
            sql(dataSource).statement("DELETE FROM ORDERS WHERE ORDER_ID='${orderId}'")
        );
    }
}

Using the traditional Java try-finally feels more natural no doubt. Please notice that the Citrus report and logging will not account the traditional finally block actions then. Good news is whatever layout you choose the outcome is always the same.

The finally block is executed safely even in case some previous test action raises an error for some reason.

5.5. Test behaviors

The concept of test behaviors is a good way to reuse test action blocks in the Java DSL. Test behaviors combine action sequences to a logical unit. The behavior defines a set of test actions that can be applied multiple times to different test cases.

The behavior is a separate Java DSL class with a single apply method that configures the test actions. Test behaviors follow this basic interface:

Test behaviors
@FunctionalInterface
public interface TestBehavior {

    /**
     * Behavior building method.
     */
    void apply(TestActionRunner runner);

}

The behavior is provided with the test action runner and all actions in the behavior should run on that runner. Every time the behavior is applied to a test the actions get executed accordingly.

Test behaviors
public class FooBehavior implements TestBehavior {
    public void apply(TestActionRunner runner) {
        runner.run(createVariable("foo", "test"));

        runner.run(echo("fooBehavior"));
    }
}

public class BarBehavior implements TestBehavior {
    public void apply(TestActionRunner runner) {
        runner.run(createVariable("bar", "test"));

        runner.run(echo("barBehavior"));
    }
}

The listing above shows two test behaviors that add very specific test actions and test variables to the test case. As you can see the test behavior is able to use the same Java DSL action methods and defines test variables and actions as a normal test case would do. You can apply the behaviors multiple times in different tests:

@CitrusTest
public void behaviorTest() {
    run(apply(new FooBehavior()));

    run(echo("Successfully applied bar behavior"));

    run(apply(new BarBehavior()));

    run(echo("Successfully applied bar behavior"));
}

The behavior is applied to the test case by calling the apply() method. As a result the behavior is executed adding its logic at this point of the test execution. The same behavior can now be called in multiple test cases so we have a reusable set of test actions.

A behavior may use different variable names then the test and vice versa. No doubt the behavior will fail as soon as special variables with respective values are not present. Unknown variables cause the behavior and the whole test to fail with errors.

So a good approach would be to harmonize variable usage across behaviors and test cases, so that templates and test cases do use the same variable naming. The behavior automatically knows all variables in the test case and all test variables created inside the behavior are visible to the test case after applying.

When a behavior changes variables this will automatically affect the variables in the whole test. So if you change a variable value inside a behavior and the variable is defined inside the test case the changes will affect the variable in a global test context. This means we have to be careful when executing a behavior several times in a test, especially in combination with parallel containers (see containers-parallel).

5.6. Run custom code

In general, you are able to mix Citrus Java DSL actions with custom Java code as you like.

Run custom code
import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.dsl.testng.TestNGCitrusSupport;

@Test
public class Service_IT extends TestNGCitrusSupport {

    private MyService myService = new MyService();

    @CitrusTest(name = "Service_IT")
    public void serviceTest() {
        run(echo("Before service call"));

        myService.doSomething("Now calling custom service");

        run(echo("After service call"));
    }
}

The test above uses a mix of Citrus test actions and custom service calls. The test logic will execute as expected. It is recommended though to wrap custom code in a test action in order to have a consistent test reporting and failure management in Citrus.

Test action wrapper
import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.dsl.testng.TestNGCitrusSupport;

@Test
public class Service_IT extends TestNGCitrusSupport {

    private MyService myService = new MyService();

    @CitrusTest(name = "Service_IT")
    public void serviceTest() {
        run(echo("Before service call"));

        run(
            action(context -> {
                myService.doSomething("Now calling custom service");
            })
        );

        run(echo("After service call"));
    }
}

The sample above wraps the call to the custom service myService in an abstract test action represented as Java lambda expression. This way the service call becomes part of the Citrus test execution and failures are reported properly. Also you have access to the current test context which holds the list of test variables as well as many other Citrus related test objects (e.g. message store).

This is why you should wrap custom code in a test action an run that code via the test action runner methods. You can also put your custom code in a test action implementation and reference the logic from multiple tests.

5.7. Bind objects to registry

The Citrus context is a place where objects can register themselves in order to enable dependency injection and instance sharing in multiple tests. Once you register the object in the context others can resolve the reference with its given name.

In a simple example the context can register a new endpoint that is injected in several tests.

You can access the Citrus context within the provided before/after methods on the test.

Register endpoint in Citrus context
public class CitrusRegisterEndpoint_IT extends TestNGCitrusSupport {

    @Override
    public void beforeSuite(CitrusContext context) {
        context.bind("foo", new FooEndpoint());
    }
}

With the CitrusContext you can bind objects to the registry. Each binding receives a name so others can resolve the instance reference for injection.

Inject endpoint in other tests
public class InjectEndpoint_IT extends TestNGCitrusSupport {

    @CitrusEndpoint
    private FooEndpoint foo;

    @Test
    @CitrusTest
    public void injectEndpointTest() {
        $(send(foo)
                .message()
                .body("Hello foo!"));

        $(receive(foo)
                .message()
                .body("Hello Citrus!"));
    }
}

The @CitrusEndpoint annotation injects the endpoint resolving the instance with the given name foo. Test methods can use this endpoint in the following in send and receive actions.

5.7.1. @BindToRegistry

An alternative to using the bind() method on the CitrusContext is to use the @BindToRegistry annotation. Methods and fields annotated will automatically register in the CitrusContext registry.

@BindToRegistry annotation
public class CitrusRegisterEndpoint_IT extends TestNGCitrusSupport {

    @CitrusFramework
    private Citrus citrus;

    @BindToRegistry(name = "fooQueue")
    private MessageQueue queue = new DefaultMessageQueue("fooQueue");

    @BindToRegistry
    public void foo() {
        return new FooEndpoint();
    }
}

The annotation is able to specify an explicit binding name. The annotation works with public methods and fields in tests.

5.7.2. Configuration classes

As an alternative to adding the registry binding configuration directly to the test you can load configuration classes.

Configuration classes are automatically loaded before a test suite run and all methods and fields are parsed for potential bindings. You can use the environment settings citrus.java.config and/or CITRUS_JAVA_CONFIG to set a default configuration class.

citrus-application.properties
citrus.java.config=MyConfig.class
MyConfig.class
public class MyConfig {

    @BindToRegistry(name = "fooQueue")
    private MessageQueue queue = new DefaultMessageQueue("fooQueue");

    @BindToRegistry
    public void foo() {
        return new FooEndpoint();
    }
}

5.7.3. @CitrusConfiguration

Each test is able to use the @CitrusConfiguration annotation to add registry bindings, too.

@CitrusConfiguration annotation
@CitrusConfiguration(classes = MyConfig.class)
public class CitrusRegisterEndpoint_IT extends TestNGCitrusSupport {

    @CitrusEndpoint
    private FooEndpoint foo;

    @Test
    @CitrusTest
    public void injectEndpointTest() {
        $(send(foo)
                .message()
                .body("Hello foo!"));

        $(receive(foo)
                .message()
                .body("Hello Citrus!"));
    }
}

The @CitrusConfiguration annotation is able to load configuration classes and bind all components to the registry for later usage. The test can inject endpoints and other components using the @CitrusEndpoint and @CitrusResource annotation on fields.

5.8. Resource injection

Resource injection is a convenient mechanism to access Citrus internal objects such as TestRunner or TestContext instances. The following sections deal with resource injection of different objects.

5.8.1. Inject Citrus framework

You can access the Citrus framework instance in order to access all components and functionalities. Just use the @CitrusFramework annotation in your test class.

Citrus framework injection
public class CitrusInjection_IT extends TestNGCitrusSupport {

    @CitrusFramework
    private Citrus citrus;

    @Test
    @CitrusTest
    public void injectCitrusTest() {
        citrus.getCitrusContext().getMessageListeners().addMessageListener(new MyListener());
    }
}

The framework instance provides access to the Citrus context which is a central registry for all components. The example above adds a new message listener.

The Citrus context is a shared component. Components added will perform with all further tests and changes made affect all tests.

5.8.2. Test action runner injection

The test action runner is the entry to the fluent Java API. You can inject the runner as a method parameter.

Test action runner injection
public class RunnerInjection_IT extends JUnit4CitrusSupport {

    @Test
    @CitrusTest
    public void injectResources(@CitrusResource TestCaseRunner runner) {

        runner.given(
            createVariable("random", "citrus:randomNumber(10)")
        );

        runner.run(
            echo("The random number is: ${random}")
        );
    }
}

The parameter requires the @CitrusResource annotations in order to mark the parameter for Citrus resource injection.

Now each method uses its own runner instances which makes sure that parallel test execution can take place without having the risk of side effects on other tests running at the same time.

5.8.3. Test context injection

The Citrus test context combines a set of central objects and functionalities that a test is able to make use of. The test context holds all variables and is able to resolve functions and validation matchers.

In general a tester will not have to explicitly access the test context because the framework is working with it behind the scenes. In terms of advanced operations and customizations accessing the test context may be a good idea though.

Each test action implementation has access to the test context as it is provided to the execution method in the interface:

Test action interface
@FunctionalInterface
public interface TestAction {
    /**
     * Main execution method doing all work
     * @param context
     */
    void execute(TestContext context);
}

In addition Citrus provides a resource injection mechanism that allows to access the current test context in a test class or test method.

Inject as method parameter
public class TestContextInjection_IT extends JUnit4CitrusSupport {

    @Test
    @CitrusTest
    public void resourceInjectionIT(@CitrusResource TestContext context) {
        context.setVariable("myVariable", "some value");

        run(echo("${myVariable}"));
    }
}

As you can see the tst method defines a parameter of type com.consol.citrus.context.TestContext. The annotation @CitrusResource tells Citrus to inject this parameter with the according instance of the context for this test.

Now you have access to the context and all its capabilities such as variable management. As an alternative you can inject the test context as a class member variable.

Inject as member
public class TestContextInjection_IT extends JUnit4CitrusSupport {

    @CitrusResource
    private TestContext context;

    @Test
    @CitrusTest
    public void resourceInjectionIT() {
        context.setVariable("myVariable", "some value");

        run(echo("${myVariable}"));
    }
}

5.8.4. Endpoint injection

Endpoints play a significant role when sending/receiving messages over various transports. An endpoint defines how to connect to a message transport (e.g. Http endpoint URL, JMS mesage broker connection, Kafka connection and topic selection).

Endpoints can live inside the Citrus context (e.g. in SPring application context) or you can inject the endpoint into the test class with given configuration.

Endpoint injection
public class EndpointInjectionJavaIT extends TestNGCitrusSpringSupport {

    @CitrusEndpoint
    @DirectEndpointConfig(queueName = "FOO.test.queue")
    private Endpoint directEndpoint;

    @Test
    @CitrusTest
    public void injectEndpoint() {
        run(send(directEndpoint)
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Hello!"));

        run(receive(directEndpoint)
                .message()
                    .type(MessageType.PLAINTEXT)
                    .body("Hello!"));
    }
}

The sample above creates a new endpoint as a direct in-memory channel endpoint. Citrus reads the @CitrusEndpoint annotation and adds the configuration as given in the @DirectEndpointConfig annotation. This way you can create and inject endpoints directly to your test.

Citrus also supports the Spring framework as a central bean registry. You can add endpoints as Spring beans and use the @Autowired annotation to inject the endpoint in your test.

6. Test variables

The usage of test variables is a core concept when writing maintainable tests. The key identifiers of a test case should be exposed as test variables at the very beginning of a test. This avoids hard coded identifiers and multiple redundant values inside the test.

Java
public void fooService_IT() {
    variable("text", "Hello Citrus!");
    variable("customerId", "123456789");

    run(echo("Text: ${text} Id: ${id}"));
}
XML
<testcase name="FooService_IT">
  <variables>
    <variable name="text" value="Hello Citrus!"/>
    <variable name="customerId" value="123456789"/>
  </variables>

  <actions>
    <echo>
      <message>Text: ${text} Id: ${id}</message>
    </echo>
  </actions>
</testcase>

Test variables help significantly when writing complex tests with lots of identifiers and semantic data. The variables are valid for the whole test case. You can reference a variable multiple times using a common variable expression ${variable-name}.

The usage of variables should make the test easier to maintain and more flexible. All essential entities and identifiers are present right at the beginning of the test, which may also give the opportunity to easily create test variants by simply changing the variable values for other test scenarios (e.g. different error codes, identifiers).

The name of the variable is arbitrary. Of course, you need to be careful with special characters and reserved XML entities like '&', '<', '>'. In general, you can apply to the Java naming convention, and you will be fine.

6.1. Global variables

You can work with different variable scopes (local or global). Local variables are accessible throughout a single test. Global variables are visible for all tests yet global variables are immutable, so tests cannot change its value.

This is a good opportunity to declare constant values for all tests. As these variables are global we need to add those to the basic Citrus context. The following example demonstrates how to add global variables in Citrus:

Java DSL
@Bean
public GlobalVariables globalVariables() {
    return new GlobalVariables.Builder()
        .variable("projectName", "Citrus Integration Testing")
        .variable("userName", "TestUser")
        .build();
}
XML DSL
<citrus:global-variables>
  <citrus:variable name="projectName" value="Citrus Integration Testing"/>
  <citrus:variable name="userName" value="TestUser"/>
</citrus:global-variables>

We add the Spring bean component to the application context file. The component receives a list of name-value variable elements. You can reference the global variables in your test cases as usual.

Another possibility to set global variables is to load those from external property files. This may give you more powerful global variables with user specific properties for instance. See how to load property files as global variables in this example:

Java DSL
@Bean
public GlobalVariablesPropertyLoader propertyLoader() {
    GlobalVariablesPropertyLoader propertyLoader = new GlobalVariablesPropertyLoader();

    propertyLoader.getPropertyFiles().add("classpath:global-variable.properties");

    return propertyLoader;
}
XML DSL
<citrus:global-variables>
  <citrus:file path="classpath:global-variable.properties"/>
</citrus:global-variables>

You can use the GlobalVariablesPropertyLoader component and add it to the context as a Spring bean. Citrus loads the given property file content as global test variables. You can mix property file and name-value pair variable definitions in the global variables component.

The global variables can have variable expressions and Citrus functions. It is possible to use previously defined global variables as values of new variables, like in this example:
global-variable.properties
user=Citrus
greeting=Hello ${user}!
date=citrus:currentDate('yyyy-MM-dd')

6.2. Extract variables

Imagine you receive a message in your test with some generated message identifier values. You have no chance to predict the identifier value because it was generated at runtime by a foreign application. You can ignore the value in order to protect your validation. In many cases you might want to save this identifier in order to use this value in the respective response message or somewhat later on in the test.

The solution is to extract dynamic values from received messages and save those to test variables at runtime.

6.2.1. JsonPath expressions

When an incoming message is passing the message validation the user can extract some values of that received message to new test variables for later use in the test.

<message type="json">
  <data>
    { "user":
      {
        "name": "Admin",
        "password": "secret",
        "admin": "true",
        "aliases": ["penny","chef","master"]
      }
    }
  </data>
  <extract>
    <message path="$.user.name" variable="userName"/>
    <message path="$.user.aliases" variable="userAliases"/>
    <message path="$.user[?(@.admin)].password" variable="adminPassword"/>
  </extract>
</message>

With this example we have extracted three new test variables via JSONPath expression evaluation. The three test variables will be available to all upcoming test actions. The variable values are:

userName=Admin
userAliases=["penny","chef","master"]
adminPassword=secret

As you can see we can also extract complex JSONObject items or JSONArray items. The test variable value is a String representation of the complex object.

6.2.2. XPath expressions

Add this code to your message receiving action.

Java DSL
@CitrusTest
public void receiveMessageTest() {
    when(
        receive("helloService")
            .extract(fromBody()
                    .expression("//TestRequest/VersionId", "versionId"))
            .extract(fromHeaders()
                    .header("Operation", "operation"))
    );

    then(
        echo("Extracted operation from header is: ${operation}")
    );

    then(
        echo("Extracted version from body is: ${versionId}")
    );
}
XML DSL
<receive endpoint="helloService">
    <message>
      ...
    </message>
    <extract>
      <header name="Operation" variable="operation"/>
      <message path="/TestRequest/VersionId" variable="versionId"/>
    </extract>
</receive>

<echo>
  <message>Extracted operation from header is: ${operation}</message>
</echo>

<echo>
  <message>Extracted version from body is: ${versionId}</message>
</echo>

As you can see Citrus is able to extract both header and message body content into test variables. The extraction will automatically create a new variable in case it does not exist. The time the variable was created all following test actions can access the test variables as usual. So you can reference the variable values in response messages or other test steps ahead.

We can also use expression result types in order to manipulate the test variable outcome. In case we use a boolean result type the existence of elements can be saved to variable values. The result type node-set translates a node list result to a comma separated string of all values in this node list. Simply use the expression result type attributes as shown in previous sections.

6.3. Path expressions

Some elements in message body might be of dynamic nature. Just think of generated identifiers or timestamps. This is the right time to use test variables and dynamic message element overwrite. You can overwrite a specific elements in the message body with path expressions (XPath or JsonPath).

6.3.1. JsonPath expressions

First thing we want to do with JsonPath is to manipulate a message content before it is actually processed. This is very useful when working with message file resources that are reused across multiple test cases. Each test case can manipulate the message content individually with JsonPath before processing the message content.

Let’s have a look at this simple sample Json message body:

Json message body user.json
{ "user":
  {
    "id": citrus:randomNumber(10),
    "name": "Unknown",
    "admin": "?",
    "projects":
      [{
        "name": "Project1",
        "status": "open"
      },
      {
        "name": "Project2",
        "status": "open"
      },
      {
        "name": "Project3",
        "status": "closed"
      }]
  }
}

Citrus can load the file content and used it as message body when sending or receiving messages in a test case. You can apply JsonPath expressions in order to manipulate the message content.

<message type="json">
  <resource file="file:path/to/user.json" />
  <element path="$.user.name" value="Admin" />
  <element path="$.user.admin" value="true" />
  <element path="$..status" value="closed" />
</message>

When all path expressions are evaluated the resulting message looks like follows:

{ "user":
  {
    "id": citrus:randomNumber(10),
    "name": "Admin",
    "admin": "true",
    "projects":
      [{
        "name": "Project1",
        "status": "closed"
      },
      {
        "name": "Project2",
        "status": "closed"
      },
      {
        "name": "Project3",
        "status": "closed"
      }]
  }
}

The JsonPath expressions set the username to Admin . The admin boolean property was set to true and all project status values were set to closed. In case a JsonPath expression should fail to find a matching element within the message structure the test case will fail.

With this JsonPath mechanism ou are able to manipulate message content before it is sent or received within Citrus. This makes life very easy when using message resource files that are reused across multiple test cases.

6.3.2. XPath expressions

In case of XML message bodies you can use XPath expressions to manipulate the body content before any message processing takes place.

XML DSL
<message>
  <payload>
    <TestMessage>
      <MessageId>${messageId}</MessageId>
      <CreatedAt>?</CreatedAt>
      <VersionId>${version}</VersionId>
    </TestMessage>
  </payload>
  <element path="/TestMessage/CreatedAt" value="${date}"/>
</message>

The program listing above shows ways of setting variable values inside a message template. First you can simply place a variable expressions inside the message (see how ${messageId} is used in the sample). In addition to that you can also use path expressions to explicitly overwrite message elements before message processing takes place.

The sample above uses an XPath expression that evaluates and searches for the right element in the message body in order to set the given value. The previously defined variable ${date} replaces the respective element value. Of course this works with XML attributes too (e.g. path expression /TestMessage/Person/@age).

Both ways via XPath or JsonPath or inline variable expressions are equal to each other. With respect to the complexity of XML namespaces and XPath you may find the inline variable expression more comfortable to use. Anyway feel free to choose the way that fits best for you.

This is how you can overwrite values in message templates in order to increase maintainability and robustness of your test.

Validation matchers put validation mechanisms to a new level offering dynamic assertion statements for validation. Have a look at the possibilities with assertion statements in validation-matcher.

6.4. Escape variables

The test variable expression syntax ${variable-name} is preserved to evaluate to a test variable within the current test context. In case the same syntax is used in one of your message content values you need to escape the syntax from being interpreted as test variable expression. You can do this by using the variable expression escaping character sequence // wrapping the actual variable name like this:

Plain text message content with escapes
This is a escaped variable expression ${//escaped//} and should not lead to unknown variable exceptions within Citrus.

The escaped expression ${//escaped//} above will result in the string ${escaped} where escaped is not treated as a test variable name but as a normal string in the message body.

This way you are able to have the same variable syntax in a message content without interfering with the Citrus variable expression syntax. As a result Citrus will not complain about not finding the test variable escaped in the current context.

The variable syntax escaping characters // are automatically removed when the expression is processed by Citrus. So we will get the following result after processing.

Parsed plain text mesage content
This is a escaped variable expression ${escaped} and should not lead to unknown variable exceptions within Citrus.

7. Message validation

When Citrus receives a message from external applications it is time to verify the message content. This message validation includes syntax rules with schema validation and message content comparison to expected templates. Citrus provides powerful message validation capabilities for different data formats. The tester is able to define expected message headers and body content. The Citrus message validator finds values not matching the expectations and reports the difference as test failure.

7.1. Validation registry

Citrus provides default message validator implementations for different data formats. The Citrus project context automatically loads these default message validators. In case one of these message validators matches the incoming message the message validator performs its validation steps with the message.

All default message validators can be overwritten by binding a component with the same id to the project context (e.g. as Spring bean in the application context).

The default message validator implementations of Citrus are:

defaultXmlMessageValidator

com.consol.citrus.validation.xml.DomXmlMessageValidator

defaultXpathMessageValidator

com.consol.citrus.validation.xml.XpathMessageValidator

defaultJsonMessageValidator

com.consol.citrus.validation.json.JsonTextMessageValidator

defaultJsonPathMessageValidator

com.consol.citrus.validation.json.JsonPathMessageValidator

defaultPlaintextMessageValidator

com.consol.citrus.validation.text.PlainTextMessageValidator

defaultMessageHeaderValidator

com.consol.citrus.validation.DefaultMessageHeaderValidator

defaultBinaryBase64MessageValidator

com.consol.citrus.validation.text.BinaryBase64MessageValidator

defaultGzipBinaryBase64MessageValidator

com.consol.citrus.validation.text.GzipBinaryBase64MessageValidator

defaultXhtmlMessageValidator

com.consol.citrus.validation.xhtml.XhtmlMessageValidator

defaultGroovyXmlMessageValidator

com.consol.citrus.validation.script.GroovyXmlMessageValidator

defaultGroovyTextMessageValidator

com.consol.citrus.validation.script.GroovyScriptMessageValidator

defaultGroovyJsonMessageValidator

com.consol.citrus.validation.script.GroovyJsonMessageValidator

You can overwrite a default message validator with a custom implementation. Just add your customized validator implementation as a bean to the Citrus context and use one of the default bean identifiers.

You can add a custom message validator as a component in the context (e.g. as Spring bean in the application context).

Java DSL
@Bean
public CustomMessageValidator customMessageValidator() {
    return new CustomMessageValidator();
}
XML DSL
<bean id="customMessageValidator" class="com.consol.citrus.validation.custom.CustomMessageValidator"/>

The listing above adds a custom message validator implementation. The message validator registry will automatically add this validator to the list of available validators in the project.

The custom implementation class has to implement the basic interface com.consol.citrus.validation.MessageValidator<>. Now Citrus will try to match the custom implementation to incoming message types and occasionally execute the message validator logic when applicable.

7.2. Validation modules

The list of available message validators in your project is controlled by the available message validator implementations on the project classpath.

You need to add validator modules to the project accordingly. For instance if you want to use the default Json message validation capabilities in Citrus you need to add the following dependency:

Json validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-json</artifactId>
  <version>${citrus.version}</version>
</dependency>

This adds the Citrus message validator component for dealing with Json message format. This includes message validators and JsonPath support. Now your Citrus project is able to validate Json messages.

Citrus provides the following validation modules:

Read more about the individual validation modules in the next sections.

7.3. Json validation

Message formats such as Json have become very popular, in particular when dealing with RESTful services. Citrus is able to expect and validate Json messages with a powerful comparison of Json structures.

By default, Citrus will use XML message formats when sending and receiving messages. This also reflects to the message validation logic Citrus uses for incoming messages. So by default Citrus will try to parse the incoming message as XML DOM element tree. In case we would like to enable Json message validation we have to tell Citrus that we expect a Json message right now.

Json message validation is not enabled by default in your project. You need to add the validation module to your project as a Maven dependency.

Json validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-json</artifactId>
  <version>${citrus.version}</version>
</dependency>

Citrus provides several default message validator implementations for Json messages:

JsonTextMessageValidator

Basic Json message validator implementation compares Json objects (expected and received). The order of Json entries can differ. Tester defines an expected control Json object with test variables and ignored entries. JsonArray as well as nested JsonObjects are supported, too.

GroovyJsonMessageValidator

Extended groovy message validator provides specific Json slurper support. With Json slurper the tester can validate the Json message body with closures for instance.

The Json validator offers two different modes to operate. By default, strict mode is enabled and the validator will also check the exact amount of object fields to match in received and control message. No additional fields in received Json data structure will be accepted then. In soft mode the validator allows additional fields in received Json data structure so the control Json object can be a partial subset in which case only the control fields are validated. Additional fields in the received Json data structure are ignored then.
The Json validation mode (strict or soft) is settable via environment variable CITRUS_JSON_MESSAGE_VALIDATION_STRICT or system property citrus.json.message.validation.strict=false. This will set soft mode to all Json text message validators.

You can also overwrite this default message validators for Json by placing a bean into the Spring Application context. The bean uses a default name as identifier. Then your custom bean will overwrite the default validator:

Java
@Bean
public JsonTextMessageValidator defaultJsonMessageValidator() {
    return new JsonTextMessageValidator();
}
XML
<bean id="defaultJsonMessageValidator" class="com.consol.citrus.validation.json.JsonTextMessageValidator"/>

The same approach applies to the Groovy message validator implementation.

Java
@Bean
public GroovyJsonMessageValidator defaultGroovyJsonMessageValidator() {
    return new GroovyJsonMessageValidator();
}
XML
<bean id="defaultGroovyJsonMessageValidator" class="com.consol.citrus.validation.script.GroovyJsonMessageValidator"/>

This is how you can customize the message validators used for Json message data.

When a message has been received in Citrus the message validation will try to find a matching message validator according to the message content. You can also specify the Json message format on a receive action in order to force Json message validation.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .body("{" +
            "\"type\" : \"read\"," +
            "\"mbean\" : \"java.lang:type=Memory\"," +
            "\"attribute\" : \"HeapMemoryUsage\"," +
            "\"path\" : \"@equalsIgnoreCase('USED')@\"," +
            "\"value\" : \"${heapUsage}\"," +
            "\"timestamp\" : \"@ignore@\"" +
          "}");
XML
<receive endpoint="someEndpoint">
    <message type="json">
        <data>
          {
            "type" : "read",
            "mbean" : "java.lang:type=Memory",
            "attribute" : "HeapMemoryUsage",
            "path" : "@equalsIgnoreCase('USED')@",
            "value" : "${heapUsage}",
            "timestamp" : "@ignore@"
          }
        </data>
    </message>
</receive>

The message receiving action in our test case specifies a message format type type="json" . This tells Citrus to look for some message validator implementation capable of validating Json messages. As we have added the proper message validator to the Spring application context Citrus will pick the right validator and Json message validation is performed on this message.

As you can see you we can use test variables as well as ignore element syntax here, too. Citrus is able to handle different Json element orders when comparing received and expected Json object. We can also use Json arrays and nested objects. The default Json message validator implementation in Citrus is very powerful in comparing Json objects.

Instead of defining an expected message body template we can also use Groovy validation scripts. Lets have a look at the Groovy Json message validator example. As usual the default Groovy Json message validator is active by default. But the special Groovy message validator implementation will only jump in when we used a validation script in our receive message definition. Let’s have an example for that.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(groovy()
        .script("assert json.type == 'read'\n" +
                "assert json.mbean == 'java.lang:type=Memory'\n" +
                "assert json.attribute == 'HeapMemoryUsage'\n" +
                "assert json.value == '${heapUsage}'"));
XML
<receive endpoint="someEndpoint">
    <message type="json">
        <validate>
            <script type="groovy">
                <![CDATA[
                  assert json.type == 'read'
                  assert json.mbean == 'java.lang:type=Memory'
                  assert json.attribute == 'HeapMemoryUsage'
                  assert json.value == '${heapUsage}'
                ]]>
            </script>
        </validate>
    </message>
</receive>

Again the message type tells Citrus that we expect a message of type json. The action uses a validation script written in Groovy to verify the incoming message. Citrus will automatically activate the special message validator that executes our Groovy script.

The script validation is very powerful as we can use the full power of the Groovy language. The validation script automatically has access to the incoming Json message object json. We can use the Groovy Json dot notated syntax in order to navigate through the Json structure. The Groovy Json slurper object json is automatically injected in the validation script. This way you can access the Json object elements in your code doing some assertions.

There is even more object injection for the validation script. With the automatically added object receivedMessage You have access to the Citrus message object for this receive action. This enables you to do whatever you want with the message body or header.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(groovy()
        .script("assert receivedMessage.getPayload(String.class).contains(\"Hello Citrus!\")\n" +
                "assert receivedMessage.getHeader("Operation") == 'sayHello'\n" +
                "context.setVariable("request_body", receivedMessage.getPayload(String.class))"));
XML
<receive endpoint="someEndpoint">
    <message type="json">
        <validate>
            <script type="groovy">
                assert receivedMessage.getPayload(String.class).contains("Hello Citrus!")
                assert receivedMessage.getHeader("Operation") == 'sayHello'

                context.setVariable("request_body", receivedMessage.getPayload(String.class))
            </script>
        </validate>
    </message>
</receive>

The listing above shows some power of the validation script. We can access the message body, we can access the message header. With test context access we can also save the whole message body as a new test variable for later usage in the test.

In general Groovy code inside the XML test case definition or as part of the Java DSL code is not very comfortable to maintain. Neither you do have code syntax assist or code completion when using inline Groovy scripts.

Also, in case the validation script gets more complex you might want to load the script from an external file resource.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(groovy()
        .script(new ClassPathResource("path/to/validationScript.groovy")));
XML
<receive endpoint="someEndpoint">
    <message type="json">
        <validate>
            <script type="groovy" file="path/to/validationScript.groovy"/>
        </validate>
    </message>
</receive>

We referenced some external file resource validationScript.groovy . This file content is loaded at runtime and is used as script body. Now that we have a normal groovy file we can use the code completion and syntax highlighting of our favorite Groovy editor.

Using several message validator implementations at the same time in the Spring application context is also no problem. Citrus automatically searches for all available message validators applicable for the given message format and executes these validators in sequence. This means that multiple message validators can coexist in a Citrus project.

Multiple message validators that all apply to the message content format will run in sequence. In case you need to explicitly choose a message validator implementation you can do so in the receive action:

Java
receive(someEndpoint)
    .validator(groovyJsonMessageValidator)
    .message()
    .type(MessageType.JSON)
    .validate(groovy()
        .script(new ClassPathResource("path/to/validationScript.groovy")));
XML
<receive endpoint="someEndpoint">
    <message type="json" validator="groovyJsonMessageValidator">
        <validate>
            <script type="groovy" file="path/to/validationScript.groovy"/>
        </validate>
    </message>
</receive>

In this example we use the groovyJsonMessageValidator explicitly in the receive test action. The message validator implementation was added as Spring bean with id groovyJsonMessageValidator to the Spring application context before. Now Citrus will only execute the explicit message validator. Other implementations that might also apply are skipped.

By default, Citrus consolidates all available message validators. You can explicitly pick a special message validator in the receive message action as shown in the example above. In this case all other validators will not take part in this special message validation. But be careful: When picking a message validator explicitly you are of course limited to this message validator capabilities. Validation features of other validators are not valid in this case (e.g. message header validation, XPath validation, etc.)

7.3.1. Ignore with JsonPath

The next usage scenario for JsonPath expressions in Citrus is the ignoring of elements during message validation. As you already know Citrus provides powerful validation mechanisms for XML and Json message format. The framework is able to compare received and expected message contents with powerful validator implementations. You can use a JsonPath expression for ignoring a very specific entry in the Json object structure.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .body("{\"users\":" +
            "[{" +
                "\"name\": \"Jane\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}," +
            "{" +
                "\"name\": \"Penny\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}," +
            "{" +
                "\"name\": \"Mary\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}]" +
        "}")
    .validate(json()
                .ignore("$.users[*].token")
                .ignore("$..lastLogin"));
XML
<receive endpoint="someEndpoint">
    <message type="json">
      <data>
        {
          "users":
          [{
            "name": "Jane",
            "token": "?",
            "lastLogin": 0
          },
          {
            "name": "Penny",
            "token": "?",
            "lastLogin": 0
          },
          {
            "name": "Mary",
            "token": "?",
            "lastLogin": 0
          }]
        }
      </data>
      <ignore expression="$.users[*].token" />
      <ignore expression="$..lastLogin" />
    </message>
</receive>

This time we add JsonPath expressions as ignore statements. This means that we explicitly leave out the evaluated elements from validation. Obviously this mechanism is a good thing to do when dynamic message data simply is not deterministic such as timestamps and dynamic identifiers. In the example above we explicitly skip the token entry and all lastLogin values that are obviously timestamp values in milliseconds.

The JsonPath evaluation is very powerful when it comes to select a set of Json objects and elements. This is how we can ignore several elements with one single JsonPath expression which is very powerful.

7.3.2. JsonPath validation

Let’s continue to use JsonPath expressions when validating a received message in Citrus:

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(jsonPath()
        .expression("$.user.name", "Penny")
        .expression("$['user']['name']", "${userName}")
        .expression("$.user.aliases", "[\"penny\",\"jenny\",\"nanny\"]")
        .expression("$.user[?(@.admin)].password", "@startsWith('$%00')@")
        .expression("$.user.address[?(@.type='office')]", "{\"city\":\"Munich\",\"street\":\"Company Street\",\"type\":\"office\"}"));
XML
<receive endpoint="someEndpoint">
  <message type="json">
    <validate>
      <json-path expression="$.user.name" value="Penny"/>
      <json-path expression="$['user']['name']" value="${userName}"/>
      <json-path expression="$.user.aliases" value="['penny','jenny','nanny']"/>
      <json-path expression="$.user[?(@.admin)].password" value="@startsWith('$%00')@"/>
      <json-path expression="$.user.address[?(@.type='office')]"
          value="{&quot;city&quot;:&quot;Munich&quot;,&quot;street&quot;:&quot;Company Street&quot;,&quot;type&quot;:&quot;office&quot;}"/>
    </validate>
  </message>
</receive>
Use path expression map
final Map<String, Object> validationMap = new HashMap<>();
validationMap.put("$.user.name", "Penny");
validationMap.put("$['user']['name']", "${userName}");
validationMap.put("$.user.aliases", "[\"penny\",\"jenny\",\"nanny\"]");
validationMap.put(""$.user[?(@.admin)].password", "@startsWith('$%00')@");
validationMap.put("$.user.address[?(@.type='office')]", "{\"city\":\"Munich\",\"street\":\"Company Street\",\"type\":\"office\"}");

receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(jsonPath().expressions(validationMap));

The above JsonPath expressions will be evaluated when Citrus validates the received message. The expression result is compared to the expected value where expectations can be static values as well as test variables and validation matcher expressions. In case a JsonPath expression should not be able to find any elements the test case will also fail.

Json is a pretty simple yet powerful message format. Simply put, a Json message just knows JsonObject, JsonArray and JsonValue items. The handling of JsonObject and JsonValue items in JsonPath expressions is straight forward. We just use a dot notated syntax for walking through the JsonObject hierarchy. The handling of JsonArray items is also not very difficult either. Citrus will try the best to convert JsonArray items to String representation values for comparison.

JsonPath expressions will only work on Json message formats. This is why we have to tell Citrus the correct message format. By default, Citrus is working with XML message data and therefore the XML validation mechanisms do apply by default. With the message type attribute set to json we make sure that Citrus enables Json specific features on the message validation such as JsonPath support.

Now lets get a bit more complex with validation matchers and Json object functions. Citrus tries to give you the most comfortable validation capabilities when comparing Json object values and Json arrays. One first thing you can use is object functions like keySet() or size() . This functionality is not covered by JsonPath out of the box but added by Citrus. See the following example on how to use it:

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(jsonPath()
        .expression("$.user.keySet()", "[id,name,admin,projects]")
        .expression("$.user.aliases.size()", "3"));
XML
<receive endpoint="someEndpoint">
  <message type="json">
    <validate>
      <json-path expression="$.user.keySet()" value="[id,name,admin,projects]"/>
      <json-path expression="$.user.aliases.size()" value="3"/>
    </validate>
  </message>
</receive>

The object functions do return special Json object related properties such as the set of keys for an object or the size of an Json array.

Now lets get even more comfortable validation capabilities with matchers. Citrus supports Hamcrest matchers which gives us a very powerful way of validating Json object elements and arrays. See the following examples that demonstrate how this works:

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .validate(jsonPath()
                .expression("$.user.keySet()", contains("id","name","admin","projects"))
                .expression("$.user.aliases.size()", allOf(greaterThan(0), lessThan(5))));
XML
<receive endpoint="someEndpoint">
  <message type="json">
    <validate>
      <json-path expression="$.user.keySet()" value="@assertThat(contains(id,name,admin,projects))@"/>
      <json-path expression="$.user.aliases.size()" value="@assertThat(allOf(greaterThan(0), lessThan(5)))@"/>
    </validate>
  </message>
</receive>

When using the XML DSL we have to use the assertThat validation matcher syntax for defining the Hamcrest matchers. You can combine matcher implementation as seen in the allOf(greaterThan(0), lessThan(5)) expression. When using the Java DSL you can just add the matcher as expected result object. Citrus evaluates the matchers and makes sure everything is as expected. This is a very powerful validation mechanism as it combines the Hamcrest matcher capabilities with Json message validation.

7.3.3. Json schema validation

The Json schema validation in Citrus is based on the drafts provided by json-schema.org. Because Json schema is a fast evolving project, only Json schema V3 and V4 are currently supported.

In contrast to the XML validation, the Json validation is an optional feature. You have to activate it withing every receive-message action by setting schema-validation="true"
Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .body()
    .validate(json()
        .schemaValidation(true)
        .schema("bookStore"));
XML
<receive endpoint="echoHttpServer">
  <message type="json" schema="bookStore" schema-validation="true">
    <data>
      {
        "isbn" : "0345391802",
        "title": "The Hitchhiker's Guide to the Galaxy",
        "author": "Douglas Adams"
      }
    </data>
  </message>
</receive>

Json schema validation in Citrus is optional and disabled by default. This is why the action required to explicitly enable the schema validation with schemaValidation(true). The schema references a bean in the Citrus context (e.g. a Spring bean in the application context). Read more about how to declare schemas in schema validation.

We encourage you to add Json schema validation to your test cases as soon as possible, because we think that message validation is a important part of integration testing.

7.3.4. Json schema repositories

Because Citrus supports different types of schema repositories, it is necessary to declare a Json schema repository as type="json". This allows Citrus to collect all Json schema files for the message validation.

Java
@Bean
public JsonSchemaRepository schemaRepository() {
    JsonSchemaRepository repository = new JsonSchemaRepository();
    repository.getSchemas().add(productSchema());
    return repository;
}
XML
<citrus:schema-repository type="json" id="jsonSchemaRepository">
    <citrus:schemas>
        <citrus:schema ref="productSchema" location="classpath:com/consol/citrus/validation/ProductsSchema.json"/>
    </citrus:schemas>
</citrus:schema-repository>

The referenced schema is another bean in the configuration that represents the schema definition.

Java
@Bean
public SimpleJsonSchema productSchema() {
    return new SimpleJsonSchema(
            new ClassPathResource("classpath:com/consol/citrus/validation/ProductsSchema.json"));
}
XML
<citrus:schema id="productSchema" location="classpath:com/consol/citrus/validation/ProductsSchema.json"/>

7.3.5. Json schema filtering and validation strategy

In reference to the current Json schema definition, it is not possible to create a direct reference between a Json message and a set of schemas, as it would be possible with XML namespaces. Because of that, Citrus follows a rule set for choosing the relevant schemas based on the configuration withing the test case in relation to the given context. The following table assumes that the Json schema validation is activated for the test action.

Scenario Validation rules

No Json schema repositories are defined in the context.

No Json schema validation applies.

There is at least one Json schema repository defined in the context.

The message of the test action must be valid regarding at least one of the available schemas within the context.

A schema overruling is configured in the test case.

The configured schema must exist and the message must be valid regarding to the specified schema.

A schema repository overruling is configured in the test case.

The configured schema repository must exist and the message must be valid regarding at least one of the schemas within the specified schema repository.

7.4. XML validation

XML is a very common message format especially in the SOAP WebServices and JMS messaging world. Citrus provides XML message validator implementations that are able to compare XML message structures. The validator will notice differences in the XML message and supports XML namespaces, attributes and XML schema validation.

XML message validation is not enabled by default in your project. You need to add the validation module to your project as a Maven dependency.

XML validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-xml</artifactId>
  <version>${citrus.version}</version>
</dependency>

The default XML message validator implementation is active by default and can be overwritten with a custom implementation using the bean id defaultXmlMessageValidator .

Java
@Bean
public DomXmlMessageValidator defaultXmlMessageValidator() {
    return new DomXmlMessageValidator();
}
XML
<bean id="defaultXmlMessageValidator" class="com.consol.citrus.validation.xml.DomXmlMessageValidator"/>

The default XML message validator is very powerful when it comes to compare XML structures. The validator supports namespaces with different prefixes and attributes als well as namespace qualified attributes. See the following sections for a detailed description of all capabilities.

7.4.1. XML payload validation

Citrus is able to verify XML message content on a received message. You as a tester can compare the whole XML message body to a predefined control message template. The Citrus message validator will walk through the XML document and compare the elements, attributes and values.

You can define the expected XML message template in multiple ways:

  • Defines the message body as nested XML message template in the test code. The whole message body is defined inside the test case.

  • Defines an expected XML message template via external file resources. This time the body content is loaded at runtime from the external file.

In Java tests that use the Citrus domain specific language you must use verbose String concatenation when constructing XML message contents inline. You need to escape reserved characters like quotes and line breaks. This is why you should consider to using external file resources in Java when dealing with large complex message data.

Java
@CitrusTest
public void externalBodyResourceTest() {
    receive("someEndpoint")
        .message()
        .body(new ClassPathResource("com/consol/citrus/message/data/TestRequest.xml"))
        .header("Operation", "sayHello")
        .header("MessageId", "${messageId}");
}
XML
<receive endpoint="someEndpoint">
  <message>
    <payload file="classpath:com/consol/citrus/message/data/TestRequest.xml"/>
  </message>
</receive>

Inline message body definition or external file resource give us a control message template that the test case expects to validate. Citrus uses this control template for extended message comparison. All elements, namespaces, attributes and node values are validated in this comparison. When using XML message bodies Citrus will navigate through the whole XML structure validating each element and its content.

Only in case received message and control message are equal to each other as expected the message validation will pass. In case differences occur the test case fails with detailed error message.

Citrus supports various ways to add dynamic message content in the message template. Secondly, Citrus can ignore some elements that should not be part of message comparison (e.g. when generated content or timestamps are part of the message content). The tester can enrich the expected message template with test variables or ignore-expressions so we get a more robust validation mechanism. We will talk about this in the next sections to come.

7.4.2. XML header validation

A message can have multiple headers in addition to the body content. You can validate headers with expected name and the control value. Just add the following header validation to your receiving action.

Java
receive("someEndpoint")
    .message()
    .header("Operation", "sayHello")
    .header("MessageId", "${messageId}");
XML
<receive endpoint="someEndpoint">
  <message>
    <header>
        <element name="Operation" value="GetCustomer"/>
        <element name="RequestTag" value="${requestTag}"/>
    </header>
  </message>
</receive>

Message headers are represented as name-value pairs. Each expected header element identified by its name has to be present in the received message. In addition to that the header value is compared to the given control value. If a header entry is not available, or the value does not match the expectations the test raises validation errors.

7.4.3. XML header fragment validation

Sometimes message headers may not apply to the name-value pair pattern. For example SOAP headers can also contain XML fragments as header values. You can add complex header data as expected value in the validation.

Java
receive("someEndpoint")
    .message()
    .header("Operation", "SayHello")
    .header("<ns0:HelloHeader xmlns:ns0=\"http://citrusframework.org/schemas/samples/HelloService.xsd\">" +
                "<ns0:MessageId>${messageId}</ns0:MessageId>" +
                "<ns0:CorrelationId>${correlationId}</ns0:CorrelationId>" +
                "<ns0:User>${user}</ns0:User>" +
                "<ns0:Text>Hello from Citrus!</ns0:Text>" +
            "</ns0:HelloHeader>");
XML
<receive endpoint="someEndpoint">
  <message>
    <header>
      <data>
        <![CDATA[
          <ns0:HelloHeader xmlns:ns0="http://citrusframework.org/schemas/samples/HelloService.xsd">
              <ns0:MessageId>${messageId}</ns0:MessageId>
              <ns0:CorrelationId>${correlationId}</ns0:CorrelationId>
              <ns0:User>${user}</ns0:User>
              <ns0:Text>Hello from Citrus!</ns0:Text>
          </ns0:HelloHeader>
        ]]>
      </data>
      <element name="Operation" value="SayHello"/>
    </header>
  </message>
</receive>

The header data has not name but uses a complex XML fragment as a value. In SOAP this header fragment will be added as a SOAP-ENV:Header then. Please read more about this in SOAP support.

7.4.4. Ignore XML elements

Some elements in the message payload might not apply for validation at all. Just think of communication timestamps or dynamic values that have been generated from a foreign service.

You as a tester may not be able to predict such a timestamp or dynamically value for expected validation. This is why you can safely ignore elements and attributes in the XML message validation.

Java
receive("someEndpoint")
    .message()
    .header("<TestMessage>" +
                "<VersionId>${versionId}</VersionId>" +
                "<Timestamp>?</Timestamp>" +
                "<MessageId>?</MessageId>" +
            "</TestMessage>")
    .validate(xpath()
            .ignore("/TestMessage/Timestamp")
            .ignore("/TestMessage/MessageId"));
XML
<receive endpoint="someEndpoint">
  <message>
    <payload>
      <TestMessage>
        <VersionId>${versionId}</VersionId>
        <Timestamp>?</Timestamp>
        <MessageId>?</MessageId>
      </TestMessage>
    </payload>
    <ignore path="/TestMessage/Timestamp"/>
    <ignore path="/TestMessage/MessageId"/>
  </message>
</receive>

The receive action above is not able to verify the elements Timestamp and MessageId. This is because the timestamp uses milliseconds and the message id has been generated by the server application. Both values must be excluded from XML validation.

You can use ignore XPath expressions that match elements in the message content that should be excluded. XPath expressions can be cumbersome and error prone though.

You can also use inline @ignore@ expressions as expected template values in order to exclude elements from valdidation. This is for those of you that do not like to write XPath expressions. As a result the ignored message elements are automatically skipped when Citrus compares and validates message contents and do not break the test case.

Java
receive("someEndpoint")
    .message()
    .header("<TestMessage>" +
                "<VersionId>${versionId}</VersionId>" +
                "<Timestamp>@ignore@</Timestamp>" +
                "<MessageId>@ignore@</MessageId>" +
            "</TestMessage>");
XML
<receive endpoint="someEndpoint">
  <message>
    <payload>
      <TestMessage>
        <VersionId>${versionId}</VersionId>
        <Timestamp>@ignore@</Timestamp>
        <MessageId>@ignore@</MessageId>
      </TestMessage>
    </payload>
  </message>
</receive>

Feel free to mix both mechanisms to ignore message elements. Ignore expressions are valid as elements, sub-tree nodes and attributes. You can use the @ignore@ placeholder in external file resources, too.

7.4.5. XPath validation

The section XML payload validation showed how to validate the complete XML message structure with control message template. All elements are validated and compared one after another.

In some cases this approach might be too extensive. Imagine the tester only needs to validate a small subset of message elements. You would rather want to use explicit element validation with XPath.

Java
receive("someEndpoint")
    .message()
    .validate(xpath()
        .expression("/TestRequest/MessageId", "${messageId}")
        .expression("//VersionId", "2"));
}
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="/TestRequest/MessageId" value="${messageId}"/>
      <xpath expression="/TestRequest/VersionId" value="2"/>
    </validate>
  </message>
</receive>

In Java the use of a map may be the easiest way to declare multiple expressions for XPath validation.

Java DSL
final Map<String, Object> expressions = new HashMap<>();
expressions.put("/TestRequest/MessageId", "${messageId}");
expressions.put("//VersionId", "2");

receive("someEndpoint")
    .message()
    .validate(xpath()
        .expressions(expressions));
}

Instead of comparing the whole message some message elements are validated explicitly via XPath. Citrus evaluates the XPath expression on the received message and compares the result value to the control value. The basic message structure as well as all other message elements are not included into this explicit validation.

If this type of element validation is chosen neither <payload> nor <data> nor <resource> template definitions are allowed in Citrus XML test cases.
Citrus offers an alternative dot-notated syntax in order to walk through XML trees. In case you are not familiar with XPath or simply need a very easy way to find your element inside the XML tree you might use this way. Every element hierarchy in the XML tree is represented with a simple dot - for example:

TestRequest.VersionId

The expression will search the XML tree for the respective <TestRequest><VersionId> element. Attributes are supported too. In case the last element in the dot-notated expression is a XML attribute the framework will automatically find it.

Of course this dot-notated syntax is very simple and might not be applicable for more complex tree navigation. XPath is much more powerful - no doubt. The dot-notated syntax might help those of you that are not familiar with XPath. So the dot-notation is supported wherever XPath expressions might apply.

The Xpath expressions can evaluate to different result types. By default, Citrus is operating on NODE and STRING result types so that you can validate some element value. But you can also use different result types such as NODESET and BOOLEAN .

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("boolean:/TestRequest/Error", false)
        .expression("number:/TestRequest/Status[.='success']", 3)
        .expression("node-set:/TestRequest/OrderType", "[single, multi, multi]");
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="/TestRequest/Error" value="false" result-type="boolean"/>
      <xpath expression="/TestRequest/Status[.='success']" value="3" result-type="number"/>
      <xpath expression="/TestRequest/OrderType" value="[single, multi, multi]" result-type="node-set"/>
    </validate>
  </message>
</receive>

In the example above we use different expression result types. First we want to make sure nor /TestRequest/Error element is present. This can be done with a boolean result type and false value. Second we want to validate the number of found elements for the expression /TestRequest/Status[.='success'] . The XPath expression evaluates to a node list that results in its list size to be checked. And last not least we evaluate to a node-set result type where all values in the node list will be translated to a comma delimited string value.

You can use even more powerful validation expressions with matcher implementations. With validation matchers you are able to use validations such as greaterThan, lessThan, hasSize and much more.

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("/TestRequest/Error", anyOf(empty(), nullValue()))
        .expression("number:/TestRequest/Status[.='success']", greaterThan(0.0))
        .expression("integer:/TestRequest/Status[.='failed']", lowerThan(1))
        .expression("node-set:/TestRequest/OrderType", hasSize(3));
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="/TestRequest/Error" value="@assertThat(anyOf(empty(), nullValue()))@"/>
      <xpath expression="/TestRequest/Status[.='success']" value="@assertThat(greaterThan(0.0))@" result-type="number"/>
      <xpath expression="/TestRequest/Status[.='failed']" value="@assertThat(lowerThan(1))@" result-type="integer"/>
      <xpath expression="/TestRequest/OrderType" value="@assertThat(hasSize(3))@" result-type="node-set"/>
    </validate>
  </message>
</receive>
The validation matchers used in the example above use the citrus-hamcrest-validation module.
XPath uses decimal number type Double by default when evaluating expressions with number result type. This means we have to use Double typed expected values, too. Citrus also provides the result type integer that automatically converts the XPath expression result to a Integer type.

When using the XML DSL we have to use the assertThat validation matcher syntax for defining the Hamcrest matcher. You can combine matcher implementation as seen in the anyOf(empty(), nullValue()) expression. When using the Java DSL you can just add the matcher as expected result object. Citrus evaluates the matchers and makes sure everything is as expected. This is a very powerful validation mechanism as it also works with node-sets containing multiple values as list.

This is how you can add very powerful message element validation in XML using XPath expressions.

7.4.6. XML namespaces

Namespaces represent an essential concept in XML. A namespace declares an element to be part of a very specific ruleset. You have to specify namespaces also when using XPath expressions. Let’s have a look at an example message that uses XML namespaces:

Sample XML body with namepaces
<ns1:TestMessage xmlns:ns1="http://citrus.com/namespace">
    <ns1:TestHeader>
        <ns1:CorrelationId>_</ns1:CorrelationId>
        <ns1:Timestamp>2001-12-17T09:30:47.0Z</ns1:Timestamp>
        <ns1:VersionId>2</ns1:VersionId>
    </ns1:TestHeader>
    <ns1:TestBody>
        <ns1:Customer>
            <ns1:Id>1</ns1:Id>
        </ns1:Customer>
    </ns1:TestBody>
</ns1:TestMessage>

Now we would like to validate some elements in this message using XPath

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("//TestMessage/TestHeader/VersionId", 2L)
        .expression("//TestMessage/TestHeader/CorrelationId", "${correlationId}");
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="//TestMessage/TestHeader/VersionId" value="2"/>
      <xpath expression="//TestMessage/TestHeader/CorrelationId" value="${correlationId}"/>
    </validate>
  </message>
</receive>

The validation will fail although the XPath expression looks correct regarding the XML tree. This is because the message uses the namespace xmlns:ns1="http://citrus.com/namespace". The XPath expression is not able to find the elements because of the missing namespace declaration in the expression. The correct XPath expression uses the namespace prefix as defined in the message.

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("//ns1:TestMessage/ns1:TestHeader/ns1:VersionId", 2L)
        .expression("//ns1:TestMessage/ns1:TestHeader/ns1:CorrelationId", "${correlationId}");
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="//ns1:TestMessage/ns1:TestHeader/ns1:VersionId" value="2"/>
      <xpath expression="//ns1:TestMessage/ns1:TestHeader/ns1:CorrelationId" value="${correlationId}"/>
    </validate>
  </message>
</receive>

Now the expressions works fine, and the validation is successful. Relying on the namespace prefix ns1 is quite error prone though. This is because the test depends on the very specific namespace prefix. As soon as the message is sent with a different namespace prefix (e.g. ns2) the validation will fail again.

You can avoid this effect when specifying your own namespace context and your own namespace prefix during validation.

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("//pfx:TestMessage/pfx:TestHeader/pfx:VersionId", 2L)
        .expression("//pfx:TestMessage/pfx:TestHeader/pfx:CorrelationId", "${correlationId}")
        .namespaceContext("pfx", "http://citrus.com/namespace"));
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="//pfx:TestMessage/pfx:TestHeader/pfx:VersionId" value="2"/>
      <xpath expression="//pfx:TestMessage/pfx:TestHeader/pfx:CorrelationId" value="${correlationId}"/>
      <namespace prefix="pfx" value="http://citrus.com/namespace"/>
    </validate>
  </message>
</receive>

Now the test is independent of any namespace prefix in the received message. The namespace context will resolve the namespaces and find the elements although the message might use different prefixes. The only thing that matters is that the namespace value (http://citrus.com/namespace) matches.

Instead of this namespace context on validation level you can also have a global namespace context which is valid in all test cases. We just add a bean in the basic Spring application context configuration which defines global namespace mappings.
Java
@Bean
public NamespaceContextBuilder namespaceContext() {
    NamespaceContextBuilder builder = new NamespaceContextBuilder();
    builder.getNamepspaceMappings().put("pfx", "http://www.consol.de/samples/sayHello");
    return builder;
}
XML
<namespace-context>
    <namespace prefix="def" uri="http://www.consol.de/samples/sayHello"/>
</namespace-context>

Once defined the def namespace prefix is valid in all test cases and all XPath expressions. This enables you to free your test cases from namespace prefix bindings that might be broken with time. You can use these global namespace mappings wherever XPath expressions are valid inside a test case (validation, ignore, extract).

In the previous section we have seen that XML namespaces can get tricky with XPath validation. Default namespaces can do even more! So lets look at the example with default namespaces:

Sample XML body with default namespaces
<TestMessage xmlns="http://citrus.com/namespace">
    <TestHeader>
        <CorrelationId>_</CorrelationId>
        <Timestamp>2001-12-17T09:30:47.0Z</Timestamp>
        <VersionId>2</VersionId>
    </TestHeader>
    <TestBody>
        <Customer>
            <Id>1</Id>
        </Customer>
    </TestBody>
</TestMessage>

The message uses default namespaces. The following approach in XPath will fail due to namespace problems.

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("//TestMessage/TestHeader/VersionId", 2L)
        .expression("//TestMessage/TestHeader/CorrelationId", "${correlationId}"));
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="//TestMessage/TestHeader/VersionId" value="2"/>
      <xpath expression="//TestMessage/TestHeader/CorrelationId" value="${correlationId}"/>
    </validate>
  </message>
</receive>

Even default namespaces need to be specified in the XPath expressions. Look at the following code listing that works fine with default namespaces:

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("//:TestMessage/:TestHeader/:VersionId", 2L)
        .expression("//:TestMessage/:TestHeader/:CorrelationId", "${correlationId}"));
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="//:TestMessage/:TestHeader/:VersionId" value="2"/>
      <xpath expression="//:TestMessage/:TestHeader/:CorrelationId" value="${correlationId}"/>
    </validate>
  </message>
</receive>
It is recommended to use the namespace context as described in the previous chapter when validating. Only this approach ensures flexibility and stable test cases regarding namespace changes.

7.4.7. Customize XML parser and serializer

When working with XML data format parsing and serializing is a common task. XML structures are parsed to a DOM (Document Object Model) representation in order to process elements, attributes and text nodes. DOM node objects get serialized to a String message payload representation. The XML parser and serializer is customizable to a certain level. By default, Citrus uses the DOM Level 3 Load and Save implementation with following settings:

Parser settings
cdata-sections

true

split-cdata-sections

false

validate-if-schema

true

element-content-whitespace

false

Serializer settings
format-pretty-print

true

split-cdata-sections

false

element-content-whitespace

true

The parameters are also described in W3C DOM configuration documentation. We can customize the default settings by adding a XmlConfigurer Spring bean to the Citrus application context.

Java
@Bean
public XmlConfigurer xmlConfigurer() {
    XmlConfigurer configurer = new XmlConfigurer();
    configurer.getParseSettings().put("validate-if-schema", false);

    configurer.getSerializeSettings().put("comments", false);
    configurer.getSerializeSettings().put("format-pretty-print", false);
    return configurer;
}
XML
<bean id="xmlConfigurer" class="com.consol.citrus.xml.XmlConfigurer">
    <property name="parseSettings">
        <map>
            <entry key="validate-if-schema" value="false" value-type="java.lang.Boolean"/>
        </map>
    </property>
    <property name="serializeSettings">
        <map>
            <entry key="comments" value="false" value-type="java.lang.Boolean"/>
            <entry key="format-pretty-print" value="false" value-type="java.lang.Boolean"/>
        </map>
    </property>
</bean>
This configuration is of global nature. All XML processing operations will be affected with this configuration.

7.4.8. Groovy XML validation

With the Groovy XmlSlurper you can easily validate XML message payloads without having to deal directly with XML. People who do not want to deal with XPath may also like this validation alternative.

The tester directly navigates through the message elements and uses simple code assertions in order to control the message content. Here is an example how to validate messages with Groovy script:

Java
receive("someEndpoint")
    .validate(groovy().script("assert root.children().size() == 4\n" +
                              "assert root.MessageId.text() == '${messageId}'\n" +
                              "assert root.CorrelationId.text() == '${correlationId}'\n")
                              "assert root.Text.text() == 'Hello ' + context.getVariable(\"user\")"))
    .header("Operation, "sayHello")
    .header("CorrelationId", "${correlationId}")
    .timeout(5000L);
XML
<receive endpoint="someEndpoint" timeout="5000">
    <message>
        <validate>
            <script type="groovy">
                assert root.children().size() == 4
                assert root.MessageId.text() == '${messageId}'
                assert root.CorrelationId.text() == '${correlationId}'
                assert root.Text.text() == 'Hello ' + context.getVariable("user")
            </script>
        </validate>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
        <element name="CorrelationId" value="${correlationId}"/>
    </header>
</receive>

The Groovy XmlSlurper validation script goes right into the message-tag instead of a XML control template or XPath validation. The Groovy script supports Java assert statements for message element validation. Citrus automatically injects the root element root to the validation script. This is the Groovy XmlSlurper object and the start of element navigation. Based on this root element you can access child elements and attributes with a dot notated syntax. Just use the element names separated by a simple dot. Very easy! If you need the list of child elements use the children() function on any element. With the text() function you get access to the element’s text-value. The size() is very useful for validating the number of child elements which completes the basic validation statements.

As you can see from the example, we may use test variables within the validation script, too. Citrus has also injected the actual test context to the validation script. The test context object holds all test variables. So you can also access variables with context.getVariable("user") for instance. On the test context you can also set new variable values with context.setVariable("user", "newUserName") .

There is even more object injection for the validation script. With the automatically added object receivedMessage You have access to the Citrus message object for this receive action. This enables you to do whatever you want with the message payload or header.

Java
receive("someEndpoint")
    .validate(groovy().script("assert receivedMessage.getPayload(String.class).contains(\"Hello Citrus!\")\n" +
                              "assert receivedMessage.getHeader("Operation") == 'sayHello'\n" +
                              "context.setVariable(\"request_payload\", receivedMessage.getPayload(String.class))"))
    .timeout(5000L);
XML
<receive endpoint="someEndpoint" timeout="5000">
    <message>
        <validate>
            <script type="groovy">
                assert receivedMessage.getPayload(String.class).contains("Hello Citrus!")
                assert receivedMessage.getHeader("Operation") == 'sayHello'

                context.setVariable("request_payload", receivedMessage.getPayload(String.class))
            </script>
        </validate>
    </message>
</receive>

The listing above shows some power of the validation script. We can access the message payload, we can access the message header. With test context access we can also save the whole message payload as a new test variable for later usage in the test.

In general Groovy code inside the XML test case definition or as part of the Java DSL code is not very comfortable to maintain. You do not have code syntax assist or code completion. This is why we can also use external file resources for the validation scripts. The syntax looks like follows:

Java
receive("someEndpoint")
    .validate(groovy()
            .script(new ClassPathResource("validationScript.groovy"))
    .timeout(5000L);
XML
<receive endpoint="someEndpoint" timeout="5000">
    <message>
        <validate>
            <script type="groovy" file="classpath:validationScript.groovy"/>
        </validate>
    </message>
</receive>

We referenced some external file resource validationScript.groovy . This file content is loaded at runtime and is used as script body. Now that we have a normal groovy file we can use the code completion and syntax highlighting of our favorite Groovy editor.

You can use the Groovy validation script in combination with other validation types like XML tree comparison and XPath validation.
For further information on the Groovy XmlSlurper please see the official Groovy website and documentation

7.4.9. XML schema validation

There are several possibilities to describe the structure of XML documents. The two most popular ways are DTD (Document type definition) and XSD (XML Schema definition). In case the XML document is classified using a schema definition the structure of the document has to fit the predefined rules inside the schema definition. XML document instances are valid only in case they meet all these structure rules defined in the schema definition. Citrus can validate XML documents using the schema languages DTD and XSD.

XSD schema repositories

Citrus tries to validate all incoming XML messages against a schema definition in order to ensure that all rules are fulfilled. This means that the message receiving actions in Citrus have to know the XML schema definition file resources that belong to our test context.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getSchemas().add(travelAgencySchema());
    repository.getSchemas().add(royalArilineSchema());
    repository.getSchemas().add(smartArilineSchema());
    return repository;
}

@Bean
public SimpleXsdSchema travelAgencySchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/flightbooking/TravelAgencySchema.xsd"));
}

@Bean
public SimpleXsdSchema royalArilineSchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/flightbooking/RoyalAirlineSchema.xsd"));
}

@Bean
public SimpleXsdSchema smartArilineSchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/flightbooking/SmartAirlineSchema.xsd"));
}
XML
<citrus:schema-repository id="schemaRepository">
    <citrus:schemas>
        <citrus:schema id="travelAgencySchema"
            location="classpath:citrus/flightbooking/TravelAgencySchema.xsd"/>
        <citrus:schema id="royalArilineSchema"
            location="classpath:citrus/flightbooking/RoyalAirlineSchema.xsd"/>
        <citrus:reference schema="smartArilineSchema"/>
    </citrus:schemas>
</citrus:schema-repository>

<citrus:schema id="smartArilineSchema"
      location="classpath:citrus/flightbooking/SmartAirlineSchema.xsd"/>

By convention there is a default schema repository component defined in the Citrus Spring application context with the id schemaRepository. Spring application context is then able to inject the schema repository into all message receiving test actions at runtime. The receiving test action consolidates the repository for a matching schema definition file in order to validate the incoming XML document structure.

The connection between incoming XML messages and xsd schema files in the repository is done by a mapping strategy which we will discuss later in this chapter. By default, Citrus picks the right schema based on the target namespace that is defined inside the schema definition. The target namespace of the schema definition has to match the namespace of the root element in the received XML message. With this mapping strategy you will not have to wire XML messages and schema files manually all is done automatically by the Citrus schema repository at runtime. All you need to do is to register all available schema definition files regardless of which target namespace or nature inside the Citrus schema repository.

XML schema validation is mandatory in Citrus. This means that Citrus always tries to find a matching schema definition inside the schema repository in order to perform syntax validation on incoming schema qualified XML messages. A classified XML message is defined by its namespace definitions. Consequently you will get validation errors in case no matching schema definition file is found inside the schema repository. So if you explicitly do not want to validate the XML schema for some reason you have to disable the validation explicitly in your test with schema-validation="false".
Java
receive("someEndpoint")
    .validate(xpath()
            .expression("//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId", "${messageId}")
            .expression("//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId", "${correlationId}")
            .schemaValidation(false)
            .namespaceContext("ns1", "http://citrus.com/namespace"))
    .timeout(5000L);
XML
<receive endpoint="someEndpoint">
    <message schema-validation="false">
      <validate>
        <xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"
             value="${messageId}"/>
        <xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"
             value="${correlationId}"/>
        <namespace prefix="ns1" value="http://citrus.com/namespace"/>
      </validate>
    </message>
</receive>

This mandatory schema validation might sound annoying to you but in our opinion it is very important to validate the structure of the received XML messages, so disabling the schema validation should not be the standard for all tests. Disabling automatic schema validation should only apply to very special situations. So please try to put all available schema definitions to the schema repository and you will be fine.

WSDL schemas

In SOAP WebServices world the WSDL (WebService Schema Definition Language) defines the structure an nature of the XML messages exchanged across the interface. Often the WSDL files do hold the XML schema definitions as nested elements. In Citrus you can directly set the WSDL file as location of a schema definition like this:

Java
@Bean
public WsdlXsdSchema arilineWsdl() {
    return new WsdlXsdSchema(
            new ClassPathResource("classpath:citrus/flightbooking/AirlineSchema.wsdl"));
}
XML
<citrus:schema id="arilineWsdl"
    location="classpath:citrus/flightbooking/AirlineSchema.wsdl"/>

Citrus is able to find the nested schema definitions inside the WSDL file in order to build a valid schema file for the schema repository. So incoming XML messages that refer to the WSDL file can be validated for syntax rules.

Schema collections

Sometimes a XML schema definition is separated into multiple files. This is a problem for the Citrus schema repository as the schema mapping strategy then is not able to pick the right file for validation, in particular when working with target namespace values as key for the schema mapping strategy. As a solution for this problem you have to put all schemas with the same target namespace value into a schema collection.

Java
@Bean
public XsdSchemaCollection flightbookingSchemaCollection() {
    XsdSchemaCollection collection = new XsdSchemaCollection();
    collection.getSchemas().add("classpath:citrus/flightbooking/BaseTypes.xsd");
    collection.getSchemas().add("classpath:citrus/flightbooking/AirlineSchema.xsd");
    return collection;
}
XML
<citrus:schema-collection id="flightbookingSchemaCollection">
  <citrus:schemas>
    <citrus:schema location="classpath:citrus/flightbooking/BaseTypes.xsd"/>
    <citrus:schema location="classpath:citrus/flightbooking/AirlineSchema.xsd"/>
  </citrus:schemas>
</citrus:schema-collection>

Both schema definitions BaseTypes.xsd and AirlineSchema.xsd share the same target namespace and therefore need to be combined in schema collection component. The schema collection can be referenced in any schema repository as normal schema definition.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getSchemas().add(flightbookingSchemaCollection());
    return repository;
}
XML
<citrus:schema-repository id="schemaRepository">
  <citrus:schemas>
    <citrus:reference schema="flightbookingSchemaCollection"/>
  </citrus:schemas>
</citrus:schema-repository>
Schema mapping strategy

The schema repository in Citrus holds one to many schema definition files and dynamically picks up the right one according to the validated message payload. The repository needs to have some strategy for deciding which schema definition to choose. See the following schema mapping strategies and decide which of them is suitable for you.

Target Namespace Mapping Strategy

This is the default schema mapping strategy. Schema definitions usually define some target namespace which is valid for all elements and types inside the schema file. The target namespace is also used as root namespace in XML message payloads. According to this information Citrus can pick up the right schema definition file in the schema repository. You can set the schema mapping strategy as property in the configuration files:

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.setSchemaMappingStrategy(schemaMappingStrategy());
    repository.getSchemas().add(helloSchema());
    return repository;
}

@Bean
public TargetNamespaceSchemaMappingStrategy schemaMappingStrategy() {
    return new TargetNamespaceSchemaMappingStrategy();
}

@Bean
public SimpleXsdSchema helloSchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/samples/sayHello.xsd"));
}
XML
<citrus:schema-repository id="schemaRepository"
    schema-mapping-strategy="schemaMappingStrategy">
  <citrus:schemas>
    <citrus:schema id="helloSchema"
        location="classpath:citrus/samples/sayHello.xsd"/>
  </citrus:schemas>
</citrus:schema-repository>

<bean id="schemaMappingStrategy"
    class="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/>

The sayHello.xsd schema file defines a target namespace (http://consol.de/schemas/sayHello.xsd):

Schema target namespace
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://consol.de/schemas/sayHello.xsd"
    targetNamespace="http://consol.de/schemas/sayHello.xsd"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified">

</xs:schema>

Incoming request messages should also have the target namespace set in the root element and this is how Citrus matches the right schema file in the repository.

HelloRequest.xml
<HelloRequest xmlns="http://consol.de/schemas/sayHello.xsd">
   <MessageId>123456789</MessageId>
   <CorrelationId>1000</CorrelationId>
   <User>Christoph</User>
   <Text>Hello Citrus</Text>
</HelloRequest>
Root QName Mapping Strategy

The next possibility for mapping incoming request messages to a schema definition is via the XML root element QName. Each XML message payload starts with a root element that usually declares the type of a XML message. According to this root element you can set up mappings in the schema repository.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.setSchemaMappingStrategy(schemaMappingStrategy());
    repository.getSchemas().add(helloSchema());
    repository.getSchemas().add(goodbyeSchema());
    return repository;
}

@Bean
public RootQNameSchemaMappingStrategy schemaMappingStrategy() {
    RootQNameSchemaMappingStrategy rootQnameStrategy = new RootQNameSchemaMappingStrategy();
    rootQnameStrategy.getMappings().put("HelloRequest", helloSchema());
    rootQnameStrategy.getMappings().put("GoodbyeRequest", goodbyeSchema());

    return rootQnameStrategy;
}

@Bean
public SimpleXsdSchema helloSchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/samples/sayHello.xsd"));
}

@Bean
public SimpleXsdSchema goodbyeSchema() {
    return new SimpleXsdSchema(
            new ClassPathResource("classpath:citrus/samples/sayGoodbye.xsd"));
}
XML
<citrus:schema-repository id="schemaRepository"
    schema-mapping-strategy="schemaMappingStrategy">
  <citrus:schemas>
    <citrus:reference schema="helloSchema"/>
    <citrus:reference schema="goodbyeSchema"/>
  </citrus:schemas>
</citrus:schema-repository>

<bean id="schemaMappingStrategy"
    class="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy">
  <property name="mappings">
    <map>
      <entry key="HelloRequest" value="helloSchema"/>
      <entry key="GoodbyeRequest" value="goodbyeSchema"/>
    </map>
  </property>
</bean>

<citrus:schema id="helloSchema"
    location="classpath:citrus/samples/sayHello.xsd"/>

<citrus:schema id="goodbyeSchema"
     location="classpath:citrus/samples/sayGoodbye.xsd"/>

The listing above defines two root qname mappings - one for HelloRequest and one for GoodbyeRequest message types. An incoming message of type <HelloRequest> is then mapped to the respective schema and so on. With this dedicated mappings you are able to control which schema is used on a XML request, regardless of target namespace definitions.

Schema mapping strategy chain

Let’s discuss the possibility to combine several schema mapping strategies in a logical chain. You can define more than one mapping strategy that are evaluated in sequence. The first strategy to find a proper schema definition file in the repository wins.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.setSchemaMappingStrategy(schemaMappingStrategy());
    repository.getSchemas().add(helloSchema());
    repository.getSchemas().add(goodbyeSchema());
    return repository;
}

@Bean
public SchemaMappingStrategyChain schemaMappingStrategy() {
    SchemaMappingStrategyChain chain = new SchemaMappingStrategyChain();

    RootQNameSchemaMappingStrategy rootQnameStrategy = new RootQNameSchemaMappingStrategy();
    rootQnameStrategy.getMappings().put("HelloRequest", helloSchema());

    chain.setStrategies(Arrays.asList(
        rootQnameStrategy,
        new TargetNamespaceSchemaMappingStrategy()
    ));

    return chain;
}
XML
<citrus:schema-repository id="schemaRepository"
    schema-mapping-strategy="schemaMappingStrategy">
  <citrus:schemas>
    <citrus:reference schema="helloSchema"/>
    <citrus:reference schema="goodbyeSchema"/>
  </citrus:schemas>
</citrus:schema-repository>

<bean id="schemaMappingStrategy"
    class="com.consol.citrus.xml.schema.SchemaMappingStrategyChain">
  <property name="strategies">
    <list>
      <bean class="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy">
        <property name="mappings">
          <map>
            <entry key="HelloRequest" value="helloSchema"/>
          </map>
        </property>
      </bean>
      <bean class="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/>
    </list>
  </property>
</bean>

So the schema mapping chain uses both RootQNameSchemaMappingStrategy and TargetNamespaceSchemaMappingStrategy in combination. In case the first root qname strategy fails to find a proper mapping the next target namespace strategy comes in and tries to find a proper schema.

DTD validation

XML DTD (document type definition) is another way to validate the structure of a XML document. Many people say that DTD is deprecated and XML schema is the much more efficient way to describe the rules of a XML structure. We do not disagree with that, but we also know that legacy systems might still use DTD. So in order to avoid validation errors we have to deal with DTD validation as well.

First thing you can do about DTD validation is to specify an inline DTD in your expected message template.

Java
receive("someEndpoint")
    .message()
    .body("<!DOCTYPE root [\n" +
                "<!ELEMENT root (message)>\n" +
                "<!ELEMENT message (text)>\n" +
                "<!ELEMENT text (#PCDATA)>\n" +
            "]>\n" +
            "<root>\n" +
                "<message>\n" +
                    "<text>Hello from Citrus!</text>\n" +
                "</message>\n" +
            "</root>")
    .timeout(5000L);
XML
<receive endpoint="someEndpoint">
    <message schema-validation="false">
        <data>
        <![CDATA[
            <!DOCTYPE root [
                <!ELEMENT root (message)>
                <!ELEMENT message (text)>
                <!ELEMENT text (#PCDATA)>
                ]>
            <root>
                <message>
                    <text>Hello from Citrus!</text>
                </message>
            </root>
        ]]>
        </data>
    </message>
</receive>

The system under test may also send the message with a inline DTD definition. So validation will succeed.

In most cases the DTD is referenced as external .dtd file resource. You can do this in your expected message template as well.

Java
receive("someEndpoint")
    .message()
    .body("<!DOCTYPE root SYSTEM \"com/consol/citrus/validation/example.dtd\">\n" +
            "<root>\n" +
                "<message>\n" +
                    "<text>Hello from Citrus!</text>\n" +
                "</message>\n" +
            "</root>")
    .timeout(5000L);
XML
<receive endpoint="someEndpoint">
    <message schema-validation="false">
        <data>
        <![CDATA[
            <!DOCTYPE root SYSTEM "com/consol/citrus/validation/example.dtd">
            <root>
                <message>
                    <text>Hello from Citrus!</text>
                </message>
            </root>
        ]]>
        <data/>
    </message>
</receive>

7.4.10. XHTML validation

Html message content is hard to verify with XML validation capabilities such as XML tree comparison or XPath support. Usually Html messages do not follow the XML well-formed rules very strictly. This implies that XML message validation will fail because of non-well-formed Html code.

XHTML closes this gap by automatically fixing the most common Html XML incompatible rule violations such as missing end tags (e.g. <br>).

Please add a new library dependency to your project. Citrus is using the jtidy library in order to prepare the HTML and XHTML messages for validation. As this 3rd party dependency is optional in Citrus we have to add it now to our project dependency list. Just add the jtidy dependency to your Maven project POM.

Jtidy library
<dependency>
    <groupId>net.sf.jtidy</groupId>
    <artifactId>jtidy</artifactId>
    <version>r938</version>
  </dependency>

Please refer to the jtidy project documentation for the latest versions. Now everything is ready. As usual the Citrus message validator for XHTML is active in background by default. You can overwrite this default implementation by placing a Spring bean with id defaultXhtmlMessageValidator to the Citrus application context.

Java
@Bean
public XhtmlMessageValidator defaultXhtmlMessageValidator() {
    return new XhtmlMessageValidator();
}
XML
<bean id="defaultXhtmlMessageValidator" class="com.consol.citrus.validation.xhtml.XhtmlMessageValidator"/>

Now you can use the XHTML message validation in your test case.

Java
receive("someEndpoint")
    .message()
    .type(MessageType.XHTML)
    .body("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"org/w3c/xhtml/xhtml1-strict.dtd\">" +
            "<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
                "<head>" +
                    "<title>Citrus Hello World</title>" +
                "</head>" +
                "<body>" +
                    "<h1>Hello World!</h1>" +
                    "<br/>" +
                    "<p>This is a test!</p>" +
                "</body>" +
            "</html>")
    .timeout(5000L);
XML
<receive endpoint="someEndpoint">
    <message type="xhtml">
        <data>
          <![CDATA[
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "org/w3c/xhtml/xhtml1-strict.dtd">
            <html xmlns="http://www.w3.org/1999/xhtml">
              <head>
                <title>Citrus Hello World</title>
              </head>
              <body>
                <h1>Hello World!</h1>
                <br/>
                <p>This is a test!</p>
              </body>
            </html>
          ]]>
        </data>
    </message>
</receive>

The message receiving action in our test case has to specify a message format type type="xhtml" . As you can see the Html message payload get XHTML specific DOCTYPE processing instruction. The xhtml1-strict.dtd is mandatory in the XHTML message validation. For better convenience all XHTML dtd files are packaged within Citrus so you can use this as a relative path.

The incoming Html message is automatically converted into proper XHTML code with well formed XML. So now the XHTML message validator can use the XML message validation mechanism of Citrus for comparing received and expected data. You can also use test variables, ignore element expressions and XPath expressions.

7.5. Schema validation

When structured data is transmitted from one system to another, it is important that both sender and receiver agree on an interface contract. The contract introduces rules of communication for both parties.

Schemas represent the most popular way to define contracts. Citrus is able to manage multiple schemas in the project context. You can define mapping rules to pick the right schema for a message validation.

Let’s start with this chapter by introducing some basic concepts of the schema validation.

7.5.1. Schema definition

Complex applications require multiple schemas that are relevant for different use cases. You should organize these schemas in your test project. First you need to add a schema definition that points to the schema location.

Java
@Bean
public SimpleXsdSchema bookstoreSchema() {
    return new SimpleXsdSchema(new ClassPathResource("classpath:com/consol/citrus/xml/BookStore.wsdl"));
}
XML
<citrus:schema id="bookstoreSchema" location="classpath:com/consol/citrus/xml/BookStore.wsdl"/>

Please keep in mind, that the id of the schema has to be unique within the context.

The samples above are using XML XSD/WSDL schema definitions. The same approach applies to Json schemas, too. You just need to use the SimpleJsonSchema class in the Java configuration. The XML configuration components derive the schema type automatically based on the file extension (.xsd, .wsdl, .json).

7.5.2. Schema repository

You can now reference the schema definition in a repository component. The repository is able to hold multiple schema definitions.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getSchemas().add(bookstoreSchema());
    return repository;
}
XML
<citrus:schema-repository id="schemaRepository">
  <citrus:schemas>
    <citrus:reference schema="bookstoreSchema" />
  </citrus:schemas>
</citrus:schema-repository>

Citrus allows you to reuse your schema definition within your context by referencing them. For a valid reference, the id of the schema and the value of the schema attribute within the reference element have to match.

As an alternative to a schema reference you can also provide the schema location in a repository.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getLocations().add("classpath:schemas/flightbooking.xsd");
    return repository;
}
XML
<citrus:schema-repository id="schemaRepository">
  <citrus:locations>
    <citrus:location path="classpath:schemas/flightbooking.xsd"/>
  </citrus:locations>
</citrus:schema-repository>

The given location points to the schema definition file. Setting all schemas one by one can be verbose and cumbersome, especially when dealing with lots of schema files. Therefore, Citrus provides schema location patterns which will import all matching schema files within the given location.

Java
@Bean
public XsdSchemaRepository schemaRepository() {
    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getLocations().add("classpath:schemas/*.xsd");
    return repository;
}
XML
<citrus:schema-repository id="schemaRepository">
  <citrus:locations>
    <citrus:location path="classpath:schemas/*.xsd"/>
  </citrus:locations>
</citrus:schema-repository>

The schema repository is able to receive many schemas with different locations and schema sources.

Java
@Bean
public SimpleXsdSchema testSchema() {
    return new SimpleXsdSchema(new ClassPathResource("classpath:com/consol/citrus/xml/test.xsd"));
}

@Bean
public XsdSchemaRepository schemaRepository() {
    SimpleXsdSchema bookstoreSchema = new SimpleXsdSchema(
            new ClassPathResource("classpath:com/consol/citrus/xml/BookStore.wsdl"));

    XsdSchemaRepository repository = new XsdSchemaRepository();
    repository.getSchemas().add(bookstoreSchema);
    repository.getSchemas().add(testSchema());
    repository.getLocations().add("classpath:schemas/*.xsd");
    return repository;
}
XML
<citrus:schema id="testSchema" location="classpath:com/consol/citrus/xml/test.xsd"/>

<citrus:schema-repository id="xmlSchemaRepository">
  <citrus:schemas>
    <citrus:schema id="bookstoreSchema" location="classpath:com/consol/citrus/xml/BookStore.wsdl"/>
    <citrus:reference schema="testSchema"/>
    <citrus:location path="classpath:schemas/*.xsd"/>
  </citrus:schemas>
</citrus:schema-repository>
The examples in this chapter have been using XML XSD schema repository components. Of course, the same components are available for Json schema repositories, too. By default, the type of the schema repository is type=xml. You can use type=json to mark the schema repository as Json nature. In Java configuration please use the JsonSchemaRepository class.

The schema repository component holds a set of schema files for a project disjoint by their type (xml, json, etc.) and identified by its unique id.

As you can see the schema repository is a simple bean defined inside the Spring application context. The repository can hold nested schema definitions, references and location definitions for all types of schema repositories.

In case you have several schema repositories in your project do always define a default repository (name="schemaRepository"). This helps Citrus to always find at least one repository to interact with.

7.5.3. Schema definition mapping

Depending on the type of message you want to validate, there are different attempts to find the correct schema for the given message. The XML schema repository will apply a mapping strategy that decides which schema should verify the current message. Citrus knows multiple mapping strategies that you can review in chapter link:#xml-schema-validation.

As a user you always have the chance to explicitly pick the right schema definition for a receive operation. You can overrule all schema mapping strategies in Citrus by directly setting the desired schema in your receiving message action.

Java
receive(httpMessageEndpoint)
    .message()
    .validate(
        xml().schema("helloSchema")
    );
XML
<receive endpoint="httpMessageEndpoint">
    <message schema="helloSchema">
      <payload>
        ...
      </payload>
    </message>
</receive>

In the example above the tester explicitly sets a schema definition in the receive action (schema="helloSchema"). The schema value refers to named schema bean defined in the project context (e.g. Spring application context).

This overrules all schema mapping strategies used in the central schema repository as the given schema is directly used for validation. This feature is helpful when dealing with different schema versions at the same time.

Another possibility would be to set a custom schema repository at this point. This means you can have more than one schema repository in your Citrus project and you pick the right one by yourself in the receive action.

Java
receive(httpMessageEndpoint)
    .message()
    .validate(
        xml().schemaRepository("helloSchemaRepository")
    );
XML
<receive endpoint="httpMessageEndpoint">
    <message schema-repository="helloSchemaRepository">
      <payload>
        ...
      </payload>
    </message>
</receive>

The schema-repository attribute refers to a Citrus schema repository component which is defined as bean in the project context.

7.6. Plain text validation

Plain text message validation performs an exact Java String match of received and expected message payloads.

Plaintext message validation is not enabled by default in your project. You need to add the validation module to your project as a Maven dependency.

Plaintext validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-text</artifactId>
  <version>${citrus.version}</version>
</dependency>

This adds the default message validator for plaintext messages. Citrus will pick this message validator for all messages of type="plaintext". The default message validator implementation can be overwritten by placing a Spring bean with id defaultPlaintextMessageValidator.

Java
@Bean
public PlainTextMessageValidator defaultPlaintextMessageValidator() {
    return new PlainTextMessageValidator();
}
XML
<bean id="defaultPlaintextMessageValidator" class="com.consol.citrus.validation.text.PlainTextMessageValidator"/>

Citrus will try to auto-guess the appropiate message validator for the incoming message. You can explicitly set the message type on the receive action so Citrus knows how to apply plaintext message validation.

Java
receive("someEndpoint")
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello World!");
XML
<receive endpoint="someEndpoint">
    <message type="plaintext">
        <data>Hello World!</data>
    </message>
</receive>

With the message format type type="plaintext" set Citrus performs String equals on the message payloads (received and expected). Only exact match will pass the test.

Of course test variables are supported in the plaintext payloads. The variables are replaced before processing the message template.

Java
receive("someEndpoint")
    .message()
    .type(MessageType.PLAINTEXT)
    .body("${hello} ${world}!");
XML
<receive endpoint="someEndpoint">
    <message type="plaintext">
        <data>${hello} ${world}!</data>
    </message>
</receive>

7.6.1. Whitespace characters

Plaintext message payloads may only differ in system-dependent line separator characters (CR, LF, CRLF). By default, the plaintext message validation fails because of that differences even if only whitespace characters are different.

You can disable this default validation behavior and ignore new line types with following system property or environment variable:

Plaintext validation settings
citrus.plaintext.validation.ignore.newline.type=true
CITRUS_PLAINTEXT_VALIDATION_IGNORE_NEWLINE_TYPE=true

In case you need to ignore all whitespaces during plaintext validation such as multiple new line characters or tabs you need to set this system property or environment variable:

Plaintext validation settings
citrus.plaintext.validation.ignore.whitespace=true
CITRUS_PLAINTEXT_VALIDATION_IGNORE_WHITESPACE=true

This property will not only ignore new line types but also normalize the whitespaces. As a result all empty lines, tabs and double whitespace characters are filtered before comparison.

Of course, you can also set the properties directly on the plaintext message validator bean:

Java
@Bean
public PlainTextMessageValidator defaultPlaintextMessageValidator() {
    PlainTextMessageValidator validatoe = new PlainTextMessageValidator();
    validator.setIgnoreNewLineType(true);
    validator.setIgnoreWhitespace(true);
    return validator;
}
XML
<bean id="defaultPlaintextMessageValidator" class="com.consol.citrus.validation.text.PlainTextMessageValidator">
  <property name="ignoreNewLineType" value="true"/>
  <property name="ignoreWhitespace" value="true"/>
</bean>

7.6.2. Ignoring text parts

The plaintext validator performs a String equals operation. Test variables are automatically replaced before that comparison takes place but what about ignore statements? The plaintext message validator is able to ignore words and character sequences based on their position in the text value. Given a source plaintext value:

Received text
Your current id is "1234567890"

In the plaintext validation you need to ignore the actual id value due to some reason. Maybe the id is generated on a foreign application and you simply do not know the actual value at runtime.

In this case we can use the common @ignore@ statement in the control message payload as follows:

Control text
Your current id is "@ignore@"

Citrus and the plaintext message validator will ignore the marked part of the text during validation. This mechanism is based on the fact that the @ignore@ statement is placed at the exact same position as the actual id value. So this mechanism requires you to know the exact structure of the plaintext value including all whitespace characters. In case Citrus finds the @ignore@ keyword in the control value the placeholder is replaced with the actual character sequence that is located at the exact same position in the source message payload that is validated.

The character sequence is defined as sequence of Java word characters. This word sequence is ending with a non-word character defined in Java (\\W which is a character that is not in [a-zA-Z_0-9]).

Instead of ignoring a single word you can also specify the amount of characters that should be ignored. This is when you have Java non-word characters that you need to ignore. Let’s have an example for that, too:

Received text
Your current id is "#12345-67890"

Given that text the simple @ignore@ statement will fail because of the non-word characters '#' and '-' that are located in the id value. This time we ignore the whole id sequence with:

Control text
Your current id is "@ignore(12)@"

This will ignore exactly 12 characters starting from the exact position of the @ignore@ keyword. So knowing that the id is exactly 12 characters long we can ignore that part.

7.6.3. Creating variables

Instead of just ignoring certain text parts we can also extract those parts into test variables. The actual character sequence is ignored during validation and in addition to that the actual value is stored to a new test variable. Given the following text payload:

Received text
Your current id is "1234567890"

And the expected control text:

Control text
Your current id is "@variable('id')@"

The validation will automatically ignore the id part in the text and create a new test variable with name id that holds the actual value. The name of the variable to create is given in the @variable()@ statement. This enables us to extract dynamic text parts that we are not able to validate. After that we can access the dynamic text part using the normal test variable syntax ${id}.

Also notice that the @variable()@ keyword expression has to be placed at the exact same position in the text as the actual value. The variable extractor will read the variable value from the source message payload starting from that position. The ending of the variable value is defined by a non-word Java character. Dashes '-' and dots '.' are automatically included in these values, too. So this will also work for you:

Received text
Today is "2017-12-24"

And the expected control text:

Control text
Today is "@variable('date')@"

This results in a new variable called date with value 2017-12-24. In addition, the European date representation works fine here, too because dots and dashes are automatically included:

Received text
Today is "24.12.2017"

7.6.4. Gzip validation

Gzip is a message compression library to optimize the message transport of large content. Citrus is able to handle compressed message payloads on send and receive operations. Sending compressed data sets the message type to gzip.

Java
send("someEndpoint")
    .message()
    .type(MessageType.GZIP)
    .body("Hello World!")
XML
<send endpoint="someEndpoint">
    <message type="gzip">
        <data>Hello World!</data>
    </message>
</send>

Just use the type="gzip" message type in the send operation. Citrus now converts the message payload to a gzip binary stream as payload.

When validating gzip binary message content the messages are compared with a given control message in binary base64 String representation. The gzip binary data is automatically unzipped and encoded as base64 character sequence in order to compare with an expected content.

The received message content is using gzip format but the actual message content does not have to be base64 encoded. Citrus is doing this conversion automatically before validation takes place. The binary data can be anything e.g. images, pdf or plaintext content.

The default message validator for gzip messages is active by default. Citrus will pick this message validator for all messages of type="gzip_base64" . The default message validator implementation can be overwritten by placing a Spring bean with id defaultGzipBinaryBase64MessageValidator to the Spring application context.

Java
@Bean
public GzipBinaryBase64MessageValidator defaultGzipBinaryBase64MessageValidator() {
    return new GzipBinaryBase64MessageValidator();
}
XML
<bean id="defaultGzipBinaryBase64MessageValidator"
      class="com.consol.citrus.validation.text.GzipBinaryBase64MessageValidator"/>

In the test case receiving action we tell Citrus to use gzip message validation.

Java
receive("someEndpoint")
    .message()
    .type(MessageType.GZIP_BASE64)
    .body("citrus:encodeBase64('Hello World!')")
XML
<receive endpoint="someEndpoint">
    <message type="gzip_base64">
        <data>citrus:encodeBase64('Hello World!')</data>
    </message>
</receive>

With the message format type type="gzip_base64" Citrus performs the gzip base64 character sequence validation. Incoming message content is automatically unzipped and encoded as base64 String and compared to the expected data. This way we can make sure that the binary content is as expected.

If you are using http client and server components the gzip compression support is built in with the underlying Spring and http commons libraries. So in http communication you just have to set the header Accept-Encoding=gzip or Content-Encoding=gzip. The message data is then automatically zipped/unzipped before Citrus gets the message data for validation. Read more about this http specific gzip compression in chapter http.

7.7. Binary validation

Binary message validation is not an easy task in particular when it comes to compare data with a given control message.

Binary message validation is not enabled by default in your project. You need to add the validation module to your project as a Maven dependency.

Binary validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-binary</artifactId>
  <version>${citrus.version}</version>
</dependency>

There are basically two ways in Citrus how to compare binary message content for validation purpose.

7.7.1. Stream message validation

A first approach to validate incoming binary message content is to compare the binary stream data with an expected stream. This comparison is straight forward as each byte in the binary stream is compared to an expected stream.

The default message validator for binary messages is active by default. Citrus will pick this message validator for all messages of type="binary_base64" . The default message validator implementation can be overwritten by placing a Spring bean with id defaultBinaryBase64MessageValidator to the Spring application context.

Java
@Bean
public BinaryMessageValidator defaultBinaryMessageValidator() {
    return new BinaryMessageValidator();
}
XML
<bean id="defaultBinaryMessageValidator" class="com.consol.citrus.validation.text.BinaryMessageValidator"/>

You can use the binary message type in a receive action in order to enable this stream comparison during validation.

Java
.receive("someEndpoint")
    .message(new DefaultMessage(FileCopyUtils.copyToByteArray(new ClassPathResource("templates/foo.png").getFile())))
    .type(MessageType.BINARY);
}
XML
<receive endpoint="someEndpoint">
    <message type="binary">
        <resource file="classpath:templates/foo.png"/>
    </message>
</receive>

It is very important to set the message type to MessageType.BINARY as this is the message type that is automatically handled by the binary stream message validator.

By the way sending binary messages in Citrus is also very easy. Just use the type="binary" message type in the send operation. Citrus now converts the message payload to a binary stream as payload.

Java
send("someEndpoint")
    .message()
    .type(MessageType.BINARY)
    .body("Hello World")
XML
<send endpoint="someEndpoint">
    <message type="binary">
        <data>Hello World!</data>
    </message>
</send>

7.7.2. Base64 message validation

Another way to validate binary message content is to use base64 String encoding. The binary data is encoded as base64 character sequence and there fore is comparable with an expected content.

The received message content does not have to be base64 encoded. Citrus is doing this conversion automatically before validation takes place. The binary data can be anything e.g. images, pdf or gzip content.

The default message validator for binary messages is active by default. Citrus will pick this message validator for all messages of type="binary_base64" . The default message validator implementation can be overwritten by placing a Spring bean with id defaultBinaryBase64MessageValidator to the Spring application context.

Java
@Bean
public BinaryBase64MessageValidator defaultBinaryBase64MessageValidator() {
    return new BinaryBase64MessageValidator();
}
XML
<bean id="defaultBinaryBase64MessageValidator" class="com.consol.citrus.validation.text.BinaryBase64MessageValidator"/>

In the test case receiving action we tell Citrus to use binary base64 message validation.

Java
receive("someEndpoint")
    .message()
    .body("citrus:encodeBase64('Hello World!')")
XML
<receive endpoint="someEndpoint">
    <message type="binary_base64">
        <data>citrus:encodeBase64('Hello World!')</data>
    </message>
</receive>

With the message format type type="binary_base64" Citrus performs the base64 character sequence validation. Incoming message content is automatically encoded as base64 String and compared to the expected data. This way we can make sure that the binary content is as expected.

Base64 encoding is also supported in outbound messages. Just use the encodeBase64 function in Citrus. The result is a base64 encoded String as message payload.

Java
send("someEndpoint")
    .message()
    .body("citrus:encodeBase64('Hello World!')")
XML
<send endpoint="someEndpoint">
    <message>
        <data>citrus:encodeBase64('Hello World!')</data>
    </message>
</send>

7.8. Hamcrest validation

Hamcrest validation empowers the validation capabilities by adding special assertions.

Hamcrest message validation capability is not enabled by default in your project. You need to add the validation module to your project as a Maven dependency.

Binary validation module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-validation-hamcrest</artifactId>
  <version>${citrus.version}</version>
</dependency>

You can use the Hamcrest assertions when receiving a message in order to validate elements, for instance with XPath.

Java
receive("someEndpoint")
    .validate(xpath()
        .expression("/TestRequest/Error", anyOf(empty(), nullValue()))
        .expression("number:/TestRequest/Status[.='success']", greaterThan(0.0))
        .expression("integer:/TestRequest/Status[.='failed']", lowerThan(1))
        .expression("node-set:/TestRequest/OrderType", hasSize(3));
XML
<receive endpoint="someEndpoint">
  <message>
    <validate>
      <xpath expression="/TestRequest/Error" value="@assertThat(anyOf(empty(), nullValue()))@"/>
      <xpath expression="/TestRequest/Status[.='success']" value="@assertThat(greaterThan(0.0))@" result-type="number"/>
      <xpath expression="/TestRequest/Status[.='failed']" value="@assertThat(lowerThan(1))@" result-type="integer"/>
      <xpath expression="/TestRequest/OrderType" value="@assertThat(hasSize(3))@" result-type="node-set"/>
    </validate>
  </message>
</receive>

7.9. Custom validation

In a previous section you have seen how to customize the global set of available message validators in a Citrus project using the validator registry.

7.9.1. Custom message validator

You can also explicitly use a custom message validator in a receive test action. This approach skips the automatic message validator resolving mechanism, and the receive action uses the defined validator implementation.

Java
receive("someEndpoint")
    .validator(groovyJsonMessageValidator)
    .message()
    .type(MessageType.JSON)
    .validate(groovy()
                .script("assert json.type == 'read'\n" +
                  "assert json.mbean == 'java.lang:type=Memory'\n" +
                  "assert json.attribute == 'HeapMemoryUsage'\n" +
                  "assert json.value == '${heapUsage}'");
XML
<receive endpoint="someEndpoint">
    <message type="json" validator="groovyJsonMessageValidator">
        <validate>
            <script type="groovy">
                <![CDATA[
                  assert json.type == 'read'
                  assert json.mbean == 'java.lang:type=Memory'
                  assert json.attribute == 'HeapMemoryUsage'
                  assert json.value == '${heapUsage}'
                ]]>
            </script>
        </validate>
    </message>
</receive>

The receive action defines the message validator to use in this specific use case. You can set a custom message validator implementation here.

Be careful when overwriting default message validation behavior. Setting a specific message validator has the effect that your receive action may not use the other default validators. The explicit validator definition in a receive action is exclusive so no further message validator resolving is performed on this receive action.

You may want to set multiple validators here in order to meet your validation requirements. For instance when setting a DomXmlMessageValidator explicitly you may not be able to use the XpathMessageValidator capabilities on that specific receive action anymore. Fortunately you can set multiple validators on a receive action that will all perform validation tasks on the received message.

Java
receive("someEndpoint")
    .validators(myXmlMessageValidator, defaultXpathMessageValidator)
    .message()
    .body("...")
    .validate(xpath().expression("//some/xpath/expression", "someControlValue"));
XML
<receive endpoint="someEndpoint">
    <message type="json" validators="myXmlMessageValidator,defaultXpathMessageValidator">
        <payload>...</data>
        <validate path="//some/xpath/expression" value="someControlValue"/>
    </message>
</receive>

7.9.2. Validation processor

The Java DSL offers some additional validation tricks and possibilities when dealing with messages that are sent and received over Citrus. One of them is the validation processor functionality. With this feature you can marshal/unmarshal message payloads and code validation steps on Java objects.

Validation processor usage
receive(bookResponseEndpoint)
    .validate(new XmlMarshallingValidationProcessor<AddBookResponseMessage>() {
        @Override
        public void validate(AddBookResponseMessage response, MessageHeaders headers) {
            Assert.isTrue(response.isSuccess());
        }
    });

By default, the validation processor needs some XML unmarshaller implementation for transforming the XML payload to a Java object. Citrus will automatically search for the unmarshaller bean in your Spring application context if nothing specific is set. Of course, you can also set the unmarshaller instance explicitly.

Use autowired unmarshaller
@Autowired
private Unmarshaller unmarshaller;

@CitrusTest
public void receiveMessageTest() {
    receive(bookResponseEndpoint)
        .validate(new XmlMarshallingValidationProcessor<AddBookResponseMessage>(unmarshaller) {
            @Override
            public void validate(AddBookResponseMessage response, MessageHeaders headers) {
                Assert.isTrue(response.isSuccess());
            }
        });
}

Obviously working on Java objects is much more comfortable than using the XML String concatenation. This is why you can also use this feature when sending messages.

Message body marshalling
@Autowired
private Marshaller marshaller;

@CitrusTest
public void sendMessageTest() {
    send(bookRequestEndpoint)
        .message()
        .body(createAddBookRequestMessage("978-citrus:randomNumber(10)"), marshaller)
        .header(SoapMessageHeaders.SOAP_ACTION, "addBook");
}

private AddBookRequestMessage createAddBookRequestMessage(String isbn) {
    AddBookRequestMessage requestMessage = new AddBookRequestMessage();
    Book book = new Book();
    book.setAuthor("Foo");
    book.setTitle("FooTitle");
    book.setIsbn(isbn);
    book.setYear(2008);
    book.setRegistrationDate(Calendar.getInstance());
    requestMessage.setBook(book);
    return requestMessage;
}

The example above creates a AddBookRequestMessage object and puts this as payload to a send action. In combination with a marshaller instance Citrus is able to create a proper XML message payload then.

8. Test actions

This chapter gives a brief description to all test actions that a tester can incorporate into the test case. Besides sending and receiving messages the tester may access these actions in order to build a more complex test scenario that fits the desired use case.

8.1. Send

Integration test scenarios need to trigger business logic on foreign applications by calling service interfaces on the system under test. The Citrus test is able to send messages over various message transports (e.g. Http REST, JMS, Kafka, filte transfer).

figure_001.jpg

A message consists of a message header (name-value pairs) and a message body. Later in this section we will see different ways of constructing a message with body and header values.

Java
import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.testng.TestNGCitrusSupport;

@Test
public class SendMessageTest extends TestNGCitrusSupport {

    @CitrusTest(name = "SendMessageTest")
    public void sendMessageTest() {
        description("Basic send message example");

        variable("text", "Hello Citrus!");
        variable("messageId", "Mx1x123456789");

        send("helloService")
                .message()
                .name("helloMessage")
                .body("<TestMessage>" +
                        "<Text>${text}</Text>" +
                      "</TestMessage>")
                .header("Operation", "sayHello")
                .header("RequestTag", "${messageId}");
    }
}
XML
<testcase name="SendMessageTest">
    <description>Basic send message example</description>

    <variables>
        <variable name="text" value="Hello Citrus!"/>
        <variable name="messageId" value="Mx1x123456789"/>
    </variables>

    <actions>
        <send endpoint="helloService">
            <message name="helloMessage">
                <payload>
                    <TestMessage>
                        <Text>${text}</Text>
                    </TestMessage>
                </payload>
            </message>
            <header>
                <element name="Operation" value="sayHello"/>
                <element name="MessageId" value="${messageId}"/>
            </header>
        </send>
    </actions>
</testcase>

The message name is optional and defines the message identifier in the local message store. This message name is very useful when accessing the message content later on during the test case. The local message store is handled per test case and contains all exchanged messages.

The sample uses both header and payload as message parts to send. In both parts you can use variable definitions (see ${text} and ${messageId}). So first of all let us recap what variables do. Test variables are defined at the very beginning of the test case and are valid throughout all actions that take place in the test. This means that actions can simply reference a variable by the expression ${variable-name} .

Use variables wherever you can! At least the important entities of a test should be defined as variables at the beginning. The test case improves maintainability and flexibility when using variables.

Now lets have a closer look at the sending action. The 'endpoint' attribute might catch your attention first. This attribute references a message endpoint in Citrus configuration by name. As previously mentioned the message endpoint definition lives in a separate configuration file and contains the actual message transport settings. In this example the "helloService" is referenced which is a message endpoint for sending out messages via JMS or HTTP for instance.

The test case is not aware of any transport details, because it does not have to. The advantages are obvious: On the one hand multiple test cases can reference the message endpoint definition for better reuse. Secondly test cases are independent of message transport details. So connection factories, user credentials, endpoint uri values and so on are not present in the test case.

In other words the "endpoint" attribute of the <send> element specifies which message endpoint definition to use and therefore where the message should go to. Once again all available message endpoints are configured in a separate Citrus configuration file. We will come to this later on. Be sure to always pick the right message endpoint type in order to publish your message to the right destination.

Now that the message sender pattern is clear we can concentrate on how to specify the message content to be sent. There are several possibilities for you to define message content in Citrus:

8.1.1. Send message body

The most important thing when dealing with sending actions is to prepare the message payload and header. You can specify the body as nested String value.

Java
send("helloService")
    .message()
    .body("...");
XML
<send endpoint="helloService">
  <message>
    <payload>
        <!-- message payload as XML -->
    </payload>
  </message>
</send>
XML CDATA
<send endpoint="helloService">
  <message>
    <data>
      <![CDATA[
        <!-- message payload as XML -->
      ]]>
    </data>
  </message>
</send>
In XML you can use nested XML elements or CDATA sections. Sometimes the nested XML message payload elements may cause XSD schema validation rule violations. This is because of variable values not fitting the XSD schema rules for example. In this scenario you could also use simple CDATA sections as payload data. In this case you need to use the `<data>` element in contrast to the `<payload>` element that we have used in our examples so far.

With this alternative you can skip the XML schema validation from your IDE at design time. Unfortunately you will lose the XSD auto completion features many XML editors offer when constructing your payload.

External file resource holding the message payload The syntax would be: <resource file="classpath:path/to/request.xml" /> The file path prefix indicates the resource type, so the file location is resolved either as file system resource (file:) or classpath resource (classpath:).

Java
send("helloService")
    .message()
    .body(new ClassPathResource("path/to/request.xml"));
XML
<send endpoint="helloService">
  <message>
    <resource file="classpath:path/to/request.xml" />
  </message>
</send>

In addition to defining message payloads as normal Strings and via external file resource (classpath and file system) you can also use model objects as payload data in Java DSL. The object will get serialized autoamtically with a marshaller or object mapper loaded from the Citrus context.

Message body model objects
send("helloService")
    .message()
    .payloadModel(new TestRequest("Hello Citrus!"));

The model object requires a proper message marshaller that should be available as bean in the project context (e.g. the Spring application context). By default, Citrus is searching for a bean of type com.consol.cirus.xml.Marshaller.

In case you have multiple message marshallers in the application context you have to tell Citrus which one to use in this particular send message action.

Explicit marshaller/mapper
send("helloService")
    .message()
    .payloadModel(new TestRequest("Hello Citrus!"), "myMessageMarshallerBean");

Now Citrus will marshal the message payload with the message marshaller bean named myMessageMarshallerBean . This way you can have multiple message marshaller implementations active in your project (XML, Json, and so on).

You can also use a Citrus message object as body. Citrus provides different message implementations with fluent APIs to have a convenient way of setting properties (e.g. HttpMessage, MailMessage, FtpMessage, SoapMessage, …​). Or you just use the default message implementation or maybe a custom implementation.

send("helloService")
    .message(new DefaultMessage("Hello World!")));

you can explicitly overwrite some message values in the body before sending takes place. You can think of overwriting specific message elements with variable values. Also you can overwrite values using XPath (xpath) or JsonPath (json-path) expressions.

Java
send(someEndpoint)
    .message()
    .body(new ClassPathResource("path/to/request.xml"))
    .process(jsonPath()
        .expression("$.user.name", "Penny")
        .expression("$['user']['name']", "${userName}"));
XML
<receive endpoint="someEndpoint">
  <message type="json">
    <resource file="classpath:path/to/request.xml" />
    <element path="$.user.name" value="Penny"/>
    <element path="$['user']['name']" value="${userName}"/>
  </message>
</receive>

8.1.2. Send message headers

Defining the message header is an essential part. So Citrus uses name-value pairs like "Operation" and "MessageId" in the next example to set message header entries. Depending on what message endpoint is used and which message transport underneath the header values will be shipped in different ways. In JMS the headers go to the header section of the message, in Http we set mime headers accordingly, in SOAP we can access the SOAP header elements and so on. Citrus aims to do the hard work for you. So Citrus knows how to set headers on different message transports.

Java
send("helloService")
    .message()
    .body("<TestMessage>" +
            "<Text>Hello!</Text>" +
        "</TestMessage>")
    .header("Operation", "sayHello");
}
XML
<send endpoint="helloService">
    <message>
        <payload>
            <TestMessage>
                <Text>Hello!</Text>
            </TestMessage>
        </payload>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
    </header>
</receive>

The message headers to send are defined by a simple name and value pair. Of course you can use test variables in header values as well.

This is basically how to send messages in Citrus. The test case is responsible for constructing the message content while the predefined message endpoint holds transport specific settings. Test cases reference endpoint components to publish messages to the outside world. The variable support in message payload and message header enables you to add dynamic values before sending out the message.

8.1.3. Groovy XML Markup builder

With the Groovy markup builder you can build XML message body content in a simple way, without having to write the typical XML overhead.

The Groovy test action support lives in a separate module. You need to add the module to your project to use the functionality.
citrus-groovy dependency module
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-groovy</artifactId>
  <version>${citrus.version}</version>
</dependency>

For example we use a Groovy script to construct the XML message to be sent out. Instead of a plain CDATA XML section or the nested body XML data we write a Groovy script snippet.

Java
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
String script = "markupBuilder.TestRequest(xmlns: 'https://citrus.schemas/samples/sayHello.xsd'){\n" +
                    "Message('Hello World!')\n" +
                "}";
messageBuilder.setPayloadBuilder(new GroovyScriptPayloadBuilder(script));

send("helloService")
    .message(messageBuilder);
XML
<send endpoint="helloService">
  <message>
    <builder type="groovy">
        markupBuilder.TestRequest(xmlns: 'https://citrus.schemas/samples/sayHello.xsd') {
            Message('Hello World!')
        }
    </builder>
  </message>
</send>

The Groovy markup builder generates the XML message body with following content:

Genereted markup
<TestRequest xmlns="https://citrus.schemas/samples/sayHello.xsd">
  <Message>Hello World</Message>
</TestRequest>

We use the builder element with type groovy and the markup builder code is directly written to this element. As you can see from the example above, you can mix XPath and Groovy markup builder code. The markup builder syntax is very easy and follows the simple rule: markupBuilder.ROOT-ELEMENT{ CHILD-ELEMENTS } . However the tester has to follow some simple rules and naming conventions when using the Citrus markup builder extension:

  • The markup builder is accessed within the script over an object named markupBuilder. The name of the custom root element follows with all its child elements.

  • Child elements may be defined within curly brackets after the root-element (the same applies for further nested child elements)

  • Attributes and element values are defined within round brackets, after the element name

  • Attribute and element values have to stand within apostrophes (e.g. attribute-name: 'attribute-value')

The Groovy markup builder script may also be used as external file resource:

Java
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
messageBuilder.setPayloadBuilder(new GroovyFileResourcePayloadBuilder("classpath:path/to/helloRequest.groovy"));

send("helloService")
    .message(messageBuilder);
XML
<send endpoint="helloService">
  <message>
    <builder type="groovy" file="classpath:path/to/helloRequest.groovy"/>
  </message>
</send>

The markup builder implementation in Groovy offers great possibilities in defining message body content. We do not need to write XML tag overhead and we can construct complex message body content with Groovy logic like iterations and conditional elements. For detailed markup builder descriptions please see the official Groovy documentation.

8.2. Receive

Receiving and validating messages is an essential part of an integration test. You can perform assertions and checks on incoming messages in order to verify that everything works as expected.

A message consists of a message header (name-value pairs) and a message body. You can specify expected message content on a receive message test action. Citrus will perform validation steps and raise errors in case the incoming message does not match these expectations.

Java
receive("helloService")
    .message()
    .name("helloRequest")
    .body("<TestMessage>" +
                "<Text>${text}</Text>" +
            "</TestMessage>")
    .header("Operation", "sayHello")
    .header("MessageId", "${messageId}");
XML
<receive endpoint="helloService">
    <message name="helloRequest">
        <payload>
            <TestMessage>
                <Text>${text}</Text>
            </TestMessage>
        </payload>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
        <element name="MessageId" value="${messageId}"/>
    </header>
</receive>

The message name is optional and defines the message identifier in the local message store. This message name is very useful when accessing the message content later on during the test case. The local message store is handled per test case and contains all exchanged messages.

The test action waits for a message to arrive. The whole test execution is blocked while waiting for the expected message. This is important to ensure the step by step test workflow processing. Of course, you can specify a message timeout setting so the receiver will only wait a given amount of time before raising a timeout error. Following from that timeout exception the test case fails as the message did not arrive in time. Citrus defines default timeout settings for all message receiving tasks.

In case the message arrives in time, the test action moves on to validate the message content as a next step. The user is able to choose from different validation capabilities. On the one hand you can specify a whole message body content that you expect as control template. In this case the received message structure is compared to the expected message content element by element. On the other hand you can use explicit element validation where only a small subset of message elements is validated.

In addition to verifying the message body content, Citrus will also perform validation on the received message header values. Test variable usage is supported as usual during the whole validation process for body and header checks.

In general the validation component (validator) in Citrus works hand in hand with a message receiving component as the following figure shows:

figure_005.jpg

The message receiving component passes the message to the validator where the individual validation steps are performed. Let us have a closer look at the validation options and features step by step.

8.2.1. Validate message body

The most detailed validation of incoming messages is to define some expected message body. The Citrus message validator will then perform a detailed message body comparison. The incoming message has to match exactly to the expected message body. The different message validator implementations in Citrus provide deep comparison of message structures such as XML, JSON and so on.

So by defining an expected message body we validate the incoming message in syntax and semantics. In case a difference is identified by the message validator the validation and the test case fails with respective exceptions. This is how you can define message body content in receive action:

Java
receive("helloService")
    .message()
    .body("...");
XML
<receive endpoint="helloService">
  <message>
    <payload>
      <!-- message payload as XML -->
    </payload>
  </message>
</receive>
XML CDATA
<receive endpoint="helloService">
  <message>
    <data>
      <![CDATA[
        <!-- message payload as XML -->
      ]]>
    </data>
  </message>
</receive>
In XML you can use nested XML elements or CDATA sections. Sometimes the nested XML message payload elements may cause XSD schema validation rule violations. This is because of variable values not fitting the XSD schema rules for example. In this scenario you could also use simple CDATA sections as payload data. In this case you need to use the `<data>` element in contrast to the `<payload>` element that we have used in our examples so far.

With this alternative you can skip the XML schema validation from your IDE at design time. Unfortunately you will lose the XSD auto completion features many XML editors offer when constructing your payload.

External file resource holding the message payload The syntax would be: <resource file="classpath:path/to/request.xml" /> The file path prefix indicates the resource type, so the file location is resolved either as file system resource (file:) or classpath resource (classpath:).

Java
receive("helloService")
    .message()
    .body(new ClassPathResource("path/to/request.xml"));
XML
<receive endpoint="helloService">
  <message>
    <resource file="classpath:path/to/request.xml" />
  </message>
</receive>

In addition to defining message payloads as normal Strings and via external file resource (classpath and file system) you can also use model objects as payload data in Java DSL. The object will get serialized autoamtically with a marshaller or object mapper loaded from the Citrus context.

Message body model objects
receive("helloService")
    .message()
    .payloadModel(new TestRequest("Hello Citrus!"));

The model object requires a proper message marshaller that should be available as bean in the project context (e.g. the Spring application context). By default, Citrus is searching for a bean of type com.consol.citrus.xml.Marshaller.

In case you have multiple message marshallers in the application context you have to tell Citrus which one to use in this particular receive message action.

Explicit marshaller/mapper
receive("helloService")
    .message()
    .payloadModel(new TestRequest("Hello Citrus!"), "myMessageMarshallerBean");

Now Citrus will marshal the message body with the message marshaller bean named myMessageMarshallerBean . This way you can have multiple message marshaller implementations active in your project (XML, JSON, and so on).

You can also use a Citrus message object as body. Citrus provides different message implementations with fluent APIs to have a convenient way of setting properties (e.g. HttpMessage, MailMessage, FtpMessage, SoapMessage, …​). Or you just use the default message implementation or maybe a custom implementation.

Citrus message object
receive("helloService")
    .message(new DefaultMessage("Hello World!")));

You can explicitly overwrite some message values in the body before validations take place. You can think of overwriting specific message elements with variable values. Also you can overwrite values using XPath (xpath) or JsonPath (json-path) expressions.

Java
receive(someEndpoint)
    .message()
    .body(new ClassPathResource("path/to/request.xml"))
    .validate(jsonPath()
        .expression("$.user.name", "Penny")
        .expression("$['user']['name']", "${userName}"));
XML
<receive endpoint="someEndpoint">
  <message type="json">
    <resource file="classpath:path/to/request.xml" />
  </message>
  <validate path="$.user.name" value="Penny"/>
  <validate path="$['user']['name']" value="${userName}"/>
</receive>

In addition to that you can ignore some elements that are skipped in comparison. We will describe this later on in this section. Now lets continue with message header validation.

8.2.2. Validate message headers

Message headers are used widely in enterprise messaging. The message headers are part of the message semantics and need to be validated, too. Citrus can validate message header by name and value.

Java
receive("helloService")
    .message()
    .body("<TestMessage>" +
            "<Text>Hello!</Text>" +
        "</TestMessage>")
    .header("Operation", "sayHello");
XML
<receive endpoint="helloService">
    <message>
        <payload>
            <TestMessage>
                <Text>Hello!</Text>
            </TestMessage>
        </payload>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
    </header>
</receive>

The expected message headers are defined by a name and value pair. Citrus will check that the expected message header is present and will check the value. In case the message header is not found or the value does not match Citrus will raise an exception and the test fails. You can use validation matchers (validation-matcher) for a more powerful validation of header values, too.

Header definition in Java DSL is straight forward as we just define name and value as usual. This completes the message validation when receiving a message in Citrus. The message validator implementations may add additional validation capabilities such as XML schema validation or XPath and JSONPath validation. Please refer to the respective chapters in this guide to learn more about that.

8.2.3. Ignore elements

Sometimes a tester can not verify all values because specifying expected values is not possible for non deterministic values (e.g. timestamps, dynamic and generated identifiers).

You can use a path expressions (e.g. Xpath, JsonPath) to ignoring a very specific entry in the message body.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .body("{\"users\":" +
            "[{" +
                "\"name\": \"Jane\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}," +
            "{" +
                "\"name\": \"Penny\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}," +
            "{" +
                "\"name\": \"Mary\"," +
                "\"token\": \"?\"," +
                "\"lastLogin\": 0" +
            "}]" +
        "}")
    .validate(json()
                .ignore("$.users[*].token")
                .ignore("$..lastLogin"));
XML
<receive endpoint="someEndpoint">
    <message type="json">
      <data>
        {
          "users":
          [{
            "name": "Jane",
            "token": "?",
            "lastLogin": 0
          },
          {
            "name": "Penny",
            "token": "?",
            "lastLogin": 0
          },
          {
            "name": "Mary",
            "token": "?",
            "lastLogin": 0
          }]
        }
      </data>
      <ignore expression="$.users[*].token" />
      <ignore expression="$..lastLogin" />
    </message>
</receive>

The sample above adds JsonPath expressions as ignore statements. This means that we explicitly leave out the evaluated elements from validation. In the example above we explicitly skip the token entry and all lastLogin values that are obviously timestamp values in milliseconds.

The path evaluation is very powerful when it comes to select a set of objects and elements. This is how you can ignore several elements with path expressions.

As an alternative you can also use the @ignore@ placholder in the message content. The placholder tells Citrus to skip the element in validation.

Java
receive(someEndpoint)
    .message()
    .type(MessageType.JSON)
    .body("{\"users\":" +
            "[{" +
                "\"name\": \"Jane\"," +
                "\"token\": \"@ignore@\"," +
                "\"lastLogin\": \"@ignore@\"" +
            "}," +
            "{" +
                "\"name\": \"Penny\"," +
                "\"token\": \"@ignore@\"," +
                "\"lastLogin\": \"@ignore@\"" +
            "}," +
            "{" +
                "\"name\": \"Mary\"," +
                "\"token\": \"@ignore@\"," +
                "\"lastLogin\": \"@ignore@\"" +
            "}]" +
        "}");
XML
<receive endpoint="someEndpoint">
    <message type="json">
      <data>
        {
          "users":
          [{
            "name": "Jane",
            "token": "@ignore@",
            "lastLogin": "@ignore@"
          },
          {
            "name": "Penny",
            "token": "@ignore@",
            "lastLogin": "@ignore@"
          },
          {
            "name": "Mary",
            "token": "@ignore@",
            "lastLogin": "@ignore@"
          }]
        }
      </data>
    </message>
</receive>
You can also ignore sub-trees in XML and whole objects and arrays in Json with the ignore expression/placeholder.
The ignore expression as well as the ignore placeholder will only skip the value matching validations for the selected element or object. The element still has to be present in the message structure. In case the element is missing for any reason the validation fails even for ignored values.

8.2.4. Message selectors

The <selector> element inside the receiving action defines key-value pairs in order to filter the messages being received. The filter applies to the message headers. This means that a receiver will only accept messages matching a header element value. In messaging applications the header information often holds message ids, correlation ids, operation names and so on. With this information given you can explicitly listen for messages that belong to your test case. This is very helpful to avoid receiving messages that are still available on the message destination.

Lets say the tested software application keeps sending messages that belong to previous test cases. This could happen in retry situations where the application error handling automatically tries to solve a communication problem that occurred during previous test cases. As a result a message destination (e.g. a JMS message queue) contains messages that are not valid any more for the currently running test case. The test case might fail because the received message does not apply to the actual use case. So we will definitely run into validation errors as the expected message control values do not match.

Now we have to find a way to avoid these problems. The test could filter the messages on a destination to only receive messages that apply for the use case that is being tested. The Java Messaging System (JMS) came up with a message header selector that will only accept messages that fit the expected header values.

Let us have a closer look at a message selector inside a receiving action:

Java
Map<String, String> selectorMap = new HasMap<>();
selectorMap.put("correlationId", "Cx1x123456789");
selectorMap.put("operation", "getOrders");

receive("someEndpoint")
    .selector(selectorMap)
    .message();
XML
<receive endpoint="someEndpoint">
  <selector>
    <element name="correlationId" value="Cx1x123456789"/>
    <element name="operation" value="getOrders"/>
  </selector>
  ...
</receive>

This example shows how message selectors work. The selector will only accept messages that meet the correlation id and the operation in the header values. All other messages on the message destination are ignored. The selector elements are automatically associated to each other using the logical AND operator. This means that the message selector string would look like this:

correlationId = 'Cx1x123456789' AND operation = 'getOrders'

Instead of using several elements in the selector you can also define a selector string directly which gives you more power in constructing the selection logic yourself. This way you can use AND logical operators yourself.

Java
receive("someEndpoint")
    .selector("correlationId='Cx1x123456789' AND operation='getOrders'")
    .message();
XML
<receive endpoint="someEndpoint">
  <selector>
    <value>
        correlationId = 'Cx1x123456789' AND operation = 'getOrders'
    </value>
  </selector>
  ...
</receive>
In case you want to run tests in parallel message selectors become essential in your test cases. The different tests running at the same time will steal messages from each other when you lack of message selection mechanisms.

8.2.5. Groovy XML Markup builder

With the Groovy markup builder you can build XML message body content in a simple way, without having to write the typical XML overhead.

The Groovy test action support lives in a separate module. You need to add the module to your project to use the functionality.
citrus-groovy dependency module
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-groovy</artifactId>
  <version>${citrus.version}</version>
</dependency>

For example we use a Groovy script to construct the XML message to be sent out. Instead of a plain CDATA XML section or the nested body XML data we write a Groovy script snippet.

Java
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
String script = "markupBuilder.TestRequest(xmlns: 'https://citrus.schemas/samples/sayHello.xsd'){\n" +
                    "Message('Hello World!')\n" +
                "}";
messageBuilder.setPayloadBuilder(new GroovyScriptPayloadBuilder(script));

receive("helloService")
    .message(messageBuilder);
XML
<receive endpoint="helloService">
  <message>
    <builder type="groovy">
        markupBuilder.TestRequest(xmlns: 'https://citrus.schemas/samples/sayHello.xsd') {
            Message('Hello World!')
        }
    </builder>
  </message>
</receive>

The Groovy markup builder generates the XML message body with following content:

Genereted markup
<TestRequest xmlns="https://citrus.schemas/samples/sayHello.xsd">
  <Message>Hello World</Message>
</TestRequest>

We use the builder element with type groovy and the markup builder code is directly written to this element. As you can see from the example above, you can mix XPath and Groovy markup builder code. The markup builder syntax is very easy and follows the simple rule: markupBuilder.ROOT-ELEMENT{ CHILD-ELEMENTS } . However the tester has to follow some simple rules and naming conventions when using the Citrus markup builder extension:

  • The markup builder is accessed within the script over an object named markupBuilder. The name of the custom root element follows with all its child elements.

  • Child elements may be defined within curly brackets after the root-element (the same applies for further nested child elements)

  • Attributes and element values are defined within round brackets, after the element name

  • Attribute and element values have to stand within apostrophes (e.g. attribute-name: 'attribute-value')

The Groovy markup builder script may also be used as external file resource:

Java
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
messageBuilder.setPayloadBuilder(new GroovyFileResourcePayloadBuilder("classpath:path/to/helloRequest.groovy"));

receive("helloService")
    .message(messageBuilder);
XML
<receive endpoint="helloService">
  <message>
    <builder type="groovy" file="classpath:path/to/helloRequest.groovy"/>
  </message>
</receive>

The markup builder implementation in Groovy offers great possibilities in defining message body content. We do not need to write XML tag overhead and we can construct complex message body content with Groovy logic like iterations and conditional elements. For detailed markup builder descriptions please see the official Groovy documentation.

8.3. SQL

In many cases it is necessary to access the database during a test. This enables a tester to also validate the persistent data in a database. It might also be helpful to prepare the database with some test data before running a test. You can do this using the two database actions that are described in the following sections.

The SQL test actions live in a separate module. You need to add the module to your project to use the actions.
citrus-sql dependency module
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-sql</artifactId>
  <version>${citrus.version}</version>
</dependency>

In general Citrus handles SELECT statements differently to other statements like INSERT, UPDATE and DELETE. When executing an SQL query with SELECT you are able to add validation steps on the result sets returned from the database. This is not allowed when executing update statements like INSERT, UPDATE, DELETE.

Do not mix statements of type SELECT with others in a single sql test action. This will lead to errors because validation steps are not valid for statements other than SELECT. Please use separate test actions for update statements.

8.3.1. SQL update, insert, delete

The <sql> action simply executes a group of SQL statements in order to change data in a database. Typically the action is used to prepare the database at the beginning of a test or to clean up the database at the end of a test. You can specify SQL statements like INSERT, UPDATE, DELETE, CREATE TABLE, ALTER TABLE and many more.

On the one hand you can specify the statements as inline SQL or stored in an external SQL resource file as shown in the next two examples.

XML DSL
<actions>
    <sql datasource="someDataSource">
        <statement>DELETE FROM CUSTOMERS</statement>
        <statement>DELETE FROM ORDERS</statement>
    </sql>

    <sql datasource="myDataSource">
        <resource file="file:tests/unit/resources/script.sql"/>
    </sql>
</actions>
Java DSL designer
@Autowired
@Qualifier("myDataSource")
private DataSource dataSource;

@CitrusTest
public void sqlTest() {
    sql(dataSource)
        .statement("DELETE FROM CUSTOMERS")
        .statement("DELETE FROM ORDERS");

    sql(dataSource)
        .sqlResource("file:tests/unit/resources/script.sql");
}
Java DSL runner
@Autowired
@Qualifier("myDataSource")
private DataSource dataSource;

@CitrusTest
public void sqlTest() {
    sql(action -> action.dataSource(dataSource)
        .statement("DELETE FROM CUSTOMERS")
        .statement("DELETE FROM ORDERS"));

    sql(action -> action.dataSource(dataSource)
        .sqlResource("file:tests/unit/resources/script.sql"));
}

The first action uses inline SQL statements defined directly inside the test case. The next action uses an external SQL resource file instead. The file resource can hold several SQL statements separated by new lines. All statements inside the file are executed sequentially by the framework.

You have to pay attention to some rules when dealing with external SQL resources.
  • Each statement should begin in a new line

  • It is not allowed to define statements with word wrapping

  • Comments begin with two dashes "–"

The external file is referenced either as file system resource or class path resource, by using the "file:" or "classpath:" prefix.

Both examples use the "datasource" attribute. This value defines the database data source to be used. The connection to a data source is mandatory, because the test case does not know about user credentials or database names. The 'datasource' attribute references predefined data sources that are located in a separate Spring configuration file.

8.3.2. SQL query

The <sql> query action is specially designed to execute SQL queries (SELECT * FROM). So the test is able to read data from a database. The query results are validated against expected data as shown in the next example.

XML DSL
<sql datasource="testDataSource">
    <statement>select NAME from CUSTOMERS where ID='${customerId}'</statement>
    <statement>select count(*) from ERRORS</statement>
    <statement>select ID from ORDERS where DESC LIKE 'Def%'</statement>
    <statement>select DESCRIPTION from ORDERS where ID='${id}'</statement>

    <validate column="ID" value="1"/>
    <validate column="NAME" value="Christoph"/>
    <validate column="COUNT(*)" value="${rowsCount}"/>
    <validate column="DESCRIPTION" value="null"/>
</sql>
Java DSL designer
@Autowired
@Qualifier("testDataSource")
private DataSource dataSource;

@CitrusTest
public void databaseQueryTest() {
    query(dataSource)
          .statement("select NAME from CUSTOMERS where CUSTOMER_ID='${customerId}'")
          .statement("select COUNT(1) as overall_cnt from ERRORS")
          .statement("select ORDER_ID from ORDERS where DESCRIPTION LIKE 'Migrate%'")
          .statement("select DESCRIPTION from ORDERS where ORDER_ID = 2")
          .validate("ORDER_ID", "1")
          .validate("NAME", "Christoph")
          .validate("OVERALL_CNT", "${rowsCount}")
          .validate("DESCRIPTION", "NULL");
}
Java DSL runner
@Autowired
@Qualifier("testDataSource")
private DataSource dataSource;

@CitrusTest
public void databaseQueryTest() {
    query(action -> action.dataSource(dataSource)
            .statement("select NAME from CUSTOMERS where CUSTOMER_ID='${customerId}'")
            .statement("select COUNT(1) as overall_cnt from ERRORS")
            .statement("select ORDER_ID from ORDERS where DESCRIPTION LIKE 'Migrate%'")
            .statement("select DESCRIPTION from ORDERS where ORDER_ID = 2")
            .validate("ORDER_ID", "1")
            .validate("NAME", "Christoph")
            .validate("OVERALL_CNT", "${rowsCount}")
            .validate("DESCRIPTION", "NULL"));
}

The action offers a wide range of validating functionality for database result sets. First of all you have to select the data via SQL statements. Here again you have the choice to use inline SQL statements or external file resource pattern.

The result sets are validated through <validate> elements. It is possible to do a detailed check on every selected column of the result set. Simply refer to the selected column name in order to validate its value. The usage of test variables is supported as well as database expressions like count(), avg(), min(), max().

You simply define the <validate> entry with the column name as the "column" attribute and any expected value expression as expected "value". The framework then will check the column to fit the expected value and raise validation errors in case of mismatch.

Looking at the first SELECT statement in the example you will see that test variables are supported in the SQL statements. The framework will replace the variable with its respective value before sending it to the database.

In the validation section variables can be used too. Look at the third validation entry, where the variable "${rowsCount}" is used. The last validation in this example shows, that NULL values are also supported as expected values.

If a single validation happens to fail, the whole action will fail with respective validation errors.

The validation with "<validate column="…​" value="…​"/>" meets single row result sets as you specify a single column control value. In case you have multiple rows in a result set you rather need to validate the columns with multiple control values like this:
  <validate column="someColumnName">
      <values>
          <value>Value in 1st row</value>
          <value>Value in 2nd row</value>
          <value>Value in 3rd row</value>
          <value>Value in x row</value>
      </values>
  </validate>

Within Java you can pass a variable argument list to the validate method like this:

query(dataSource)
    .statement("select NAME from WEEKDAYS where NAME LIKE 'S%'")
    .validate("NAME", "Saturday", "Sunday")

Next example shows how to work with multiple row result sets and multiple values to expect within one column:

  <sql datasource="testDataSource">
      <statement>select WEEKDAY as DAY, DESCRIPTION from WEEK</statement>
      <validate column="DAY">
          <values>
              <value>Monday</value>
              <value>Tuesday</value>
              <value>Wednesday</value>
              <value>Thursday</value>
              <value>Friday</value>
              <value>@ignore@</value>
              <value>@ignore@</value>
          </values>
      </validate>
      <validate column="DESCRIPTION">
          <values>
              <value>I hate Mondays!</value>
              <value>Tuesday is sports day</value>
              <value>The mid of the week</value>
              <value>Thursday we play chess</value>
              <value>Friday, the weekend is near!</value>
              <value>@ignore@</value>
              <value>@ignore@</value>
          </values>
      </validate>
  </sql>

For the validation of multiple rows the `<validate>` element is able to host a list of control values for a column. As you can see from the example above, you have to add a control value for each row in the result set. This also means that we have to take care of the total number of rows. Fortunately we can use the ignore placeholder, in order to skip the validation of a specific row in the result set. Functions and variables are supported as usual.

It is important, that the control values are defined in the correct order, because they are compared one on one with the actual result set coming from database query. You may need to add "order by" SQL expressions to get the right order of rows returned. If any of the values fails in validation or the total number of rows is not equal, the whole action will fail with respective validation errors.

8.3.3. Transaction management

By default no transactions are used when Citrus executes SQL statements on a datasource. You can enable transaction management by selecting a transaction manager.

XML DSL
<actions>
    <sql datasource="someDataSource"
         transaction-manager="someTransactionManager"
         transaction-timeout="15000"
         transaction-isolation-level="ISOLATION_READ_COMMITTED">
        <statement>DELETE FROM CUSTOMERS</statement>
        <statement>DELETE FROM ORDERS</statement>
    </sql>
</actions>
Java DSL
@Autowired
@Qualifier("myDataSource")
private DataSource dataSource;

@CitrusTest
public void sqlTest() {
    sql(dataSource)
        .transactionManager(transactionManager)
        .transactionTimeout(15000)
        .transactionIsolationLevel("ISOLATION_READ_COMMITTED")
        .statement("DELETE FROM CUSTOMERS")
        .statement("DELETE FROM ORDERS");
}

The transaction-manager attribute references a Spring bean of type "org.springframework.transaction.PlatformTransactionManager". You can add this transaction manager to the Spring bean configuration:

<bean id="someTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="someDataSource"/>
</bean>

The transaction isolation level as well as the transaction timeout get set on the transaction definition used during SQL statement execution. The isolation level should evaluate to one of the constants given in "org.springframework.transaction.TransactionDefinition". Valid isolation level are:

  • ISOLATION_DEFAULT

  • ISOLATION_READ_UNCOMMITTED

  • ISOLATION_READ_COMMITTED

  • ISOLATION_REPEATABLE_READ

  • ISOLATION_SERIALIZABLE

8.3.4. Groovy SQL result set validation

Groovy provides great support for accessing Java list objects and maps. As a Java SQL result set is nothing but a list of map representations, where each entry in the list defines a row in the result set and each map entry represents the columns and values. So with Groovy’s list and map access we have great possibilities to validate a SQL result set - out of the box.

XML DSL
<sql datasource="testDataSource">
    <statement>select ID from CUSTOMERS where NAME='${customerName}'</statement>
    <statement>select ORDERTYPE, STATUS from ORDERS where ID='${orderId}'</statement>

    <validate-script type="groovy">
        assert rows.size() == 2
        assert rows[0].ID == '1'
        assert rows[1].STATUS == 'in progress'
        assert rows[1] == [ORDERTYPE:'SampleOrder', STATUS:'in progress']
    </validate-script>
</sql>
Java DSL designer
query(dataSource)
    .statement("select ORDERTYPE, STATUS from ORDERS where ID='${orderId}'")
    .validateScript("assert rows.size() == 2;" +
            "assert rows[0].ID == '1';" +
            "assert rows[0].STATUS == 'in progress';", "groovy");
Java DSL runner
query(action -> action.dataSource(dataSource)
    .statement("select ORDERTYPE, STATUS from ORDERS where ID='${orderId}'")
    .validateScript("assert rows.size() == 2;" +
            "assert rows[0].ID == '1';" +
            "assert rows[0].STATUS == 'in progress';", "groovy"));

As you can see Groovy provides fantastic access methods to the SQL result set. We can browse the result set with named column values and check the size of the result set. We are also able to search for an entry, iterate over the result set and have other helpful operations. For a detailed description of the list and map handling in Groovy my advice for you is to have a look at the official Groovy documentation.

In general other script languages do also support this kind of list and map access. For now we just have implemented the Groovy script support, but the framework is ready to work with all other great script languages out there, too (e.g. Scala, Clojure, Fantom, etc.). So if you prefer to work with another language join and help us implement those features.

8.3.5. Save result set values

Now the validation of database entries is a very powerful feature but sometimes we simply do not know the persisted content values. The test may want to read database entries into test variables without validation. Citrus is able to do that with the following <extract> expressions:

XML DSL
<sql datasource="testDataSource">
    <statement>select ID from CUSTOMERS where NAME='${customerName}'</statement>
    <statement>select STATUS from ORDERS where ID='${orderId}'</statement>

    <extract column="ID" variable="${customerId}"/>
    <extract column="STATUS" variable="${orderStatus}"/>
</sql>
Java DSL designer
query(dataSource)
    .statement("select STATUS from ORDERS where ID='${orderId}'")
    .extract("STATUS", "orderStatus");
Java DSL runner
query(action -> action.dataSource(dataSource)
    .statement("select STATUS from ORDERS where ID='${orderId}'")
    .extract("STATUS", "orderStatus"));

We can save the database column values directly to test variables. Of course you can combine the value extraction with the normal column validation described earlier in this chapter. Please keep in mind that we can not use these operations on result sets with multiple rows. Citrus will always use the first row in a result set.

8.4. Sleep

This action shows how to make the test framework sleep for a given amount of time. The attribute 'time' defines the amount of time to wait in seconds. As shown in the next example decimal values are supported too. When no waiting time is specified the default time of 50000 milliseconds applies.

XML DSL
<testcase name="sleepTest">
    <actions>
        <sleep seconds="3.5"/>

        <sleep milliseconds="500"/>

        <sleep/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void sleepTest() {
    sleep(500); // sleep 500 milliseconds

    sleep(); // sleep default time
}

When should somebody use this action? To us this action was always very useful in case the test needed to wait until an application had done some work. For example in some cases the application took some time to write some data into the database. We waited then a small amount of time in order to avoid unnecessary test failures, because the test framework simply validated the database too early. Or as another example the test may wait a given time until retry mechanisms are triggered in the tested application and then proceed with the test actions.

8.5. Java

The test framework is written in Java and runs inside a Java virtual machine. The functionality of calling other Java objects and methods in this same Java VM through Java Reflection is self-evident. With this action you can call any Java API available at runtime through the specified Java classpath.

The action syntax looks like follows:

<java class="com.consol.citrus.test.util.InvocationDummy">
    <constructor>
        <argument type="">Test Invocation</argument>
    </constructor>
    <method name="invoke">
        <argument type="String[]">1,2</argument>
    </method>
</java>

<java class="com.consol.citrus.test.util.InvocationDummy">
    <constructor>
        <argument type="">Test Invocation</argument>
    </constructor>
    <method name="invoke">
        <argument type="int">4</argument>
        <argument type="String">Test Invocation</argument>
        <argument type="boolean">true</argument>
    </method>
</java>

<java class="com.consol.citrus.test.util.InvocationDummy">
    <method name="main">
        <argument type="String[]">4,Test,true </argument>
    </method>
</java>

The Java class is specified by fully qualified class name. Constructor arguments are added using the <constructor> element with a list of <argument> child elements. The type of the argument is defined within the respective attribute "type". By default the type would be String.

The invoked method on the Java object is simply referenced by its name. Method arguments do not bring anything new after knowing the constructor argument definition, do they?.

Method arguments support data type conversion too, even string arrays (useful when calling CLIs). In the third action in the example code you can see that colon separated strings are automatically converted to string arrays.

Simple data types are defined by their name (int, boolean, float etc.). Be sure that the invoked method and class constructor fit your arguments and vice versa, otherwise you will cause errors at runtime.

Besides instantiating a fully new object instance for a class how about reusing a bean instance available in Spring bean container. Simply use the ref attribute and refer to an existing bean in Spring application context.

<java ref="invocationDummy">
    <method name="invoke">
        <argument type="int">4</argument>
        <argument type="String">Test Invocation</argument>
        <argument type="boolean">true</argument>
    </method>
</java>

<bean id="invocationDummy" class="com.consol.citrus.test.util.InvocationDummy"/>

The method is invoked on the Spring bean instance. This is very useful as you can inject other objects (e.g. via Autowiring) to the Spring bean instance before method invocation in test takes place. This enables you to execute any Java logic inside a test case.

8.6. Receive timeout

In some cases it might be necessary to validate that a message is not present on a destination. This means that this action expects a timeout when receiving a message from an endpoint destination. For instance the tester intends to ensure that no message is sent to a certain destination in a time period. In that case the timeout would not be a test aborting error but the expected behavior. And in contrast to the normal behavior when a message is received in the time period the test will fail with error.

In order to validate such a timeout situation the action <expectTimout> shall help. The usage is very simple as the following example shows:

XML DSL
<testcase name="receiveJMSTimeoutTest">
    <actions>
        <expect-timeout endpoint="myEndpoint" wait="500"/>
    </actions>
</testcase>
Java DSL designer
@Autowired
@Qualifier("myEndpoint")
private Endpoint myEndpoint;

@CitrusTest
public void receiveTimeoutTest() {
    receiveTimeout(myEndpoint)
        .timeout(500);
}
Java DSL runner
@Autowired
@Qualifier("myEndpoint")
private Endpoint myEndpoint;

@CitrusTest
public void receiveTimeoutTest() {
    receiveTimeout(action -> action.endpoint(myEndpoint)
                    .timeout(500));
}

The action offers two attributes:

endpoint

Reference to a message endpoint that will try to receive messages.

wait/timeout

Time period to wait for messages to arrive

Sometimes you may want to add some selector on the timeout receiving action. This way you can very selective check on a message to not be present on a message destination. This is possible with defining a message selector on the test action as follows.

XML DSL
<expect-timeout endpoint="myEndpoint" wait="500">
  <select>MessageId='123456789'<select/>
<expect-timeout/>
Java DSL designer
@CitrusTest
public void receiveTimeoutTest() {
    receiveTimeout(myEndpoint)
        .selector("MessageId = '123456789'")
        .timeout(500);
}
Java DSL runner
@CitrusTest
public void receiveTimeoutTest() {
    receiveTimeout(action -> action.endpoint(myEndpoint)
                    .selector("MessageId = '123456789'")
                    .timeout(500));
}

8.7. Echo

The <echo> action prints messages to the console/logger. This functionality is useful when debugging test runs. The property "message" defines the text that is printed. Tester might use it to print out debug messages and variables as shown the next code example:

XML DSL
<testcase name="echoTest">
    <variables>
        <variable name="date" value="citrus:currentDate()"/>
    </variables>
    <actions>
        <echo>
            <message>Hello Test Framework</message>
        </echo>

        <echo>
            <message>Current date is: ${date}</message>
        </echo>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void echoTest() {
    variable("date", "citrus:currentDate()");

    echo("Hello Test Framework");
    echo("Current date is: ${date}");
}

Result on the console:

Hello Test Framework
Current time is: 05.08.2008

8.8. Stop time

Time measurement during a test can be very helpful. The <trace-time> action creates and monitors multiple time lines. The action offers the attribute id to identify a time line. The tester can of course use more than one time line with different ids simultaneously.

Read the next example and you will understand the mix of different time lines:

XML DSL
<testcase name="StopTimeTest">
  <actions>
    <trace-time/>

    <trace-time id="time_line_id"/>

    <sleep seconds="3.5"/>

    <trace-time id=" time_line_id "/>

    <sleep milliseconds="5000"/>

    <trace-time/>

    <trace-time id=" time_line_id "/>
  </actions>
</testcase>
Java DSL
@CitrusTest
public void stopTimeTest() {
    stopTime();
    stopTime("time_line_id");
    sleep(3.5); // do something
    stopTime("time_line_id");
    sleep(5000); // do something
    stopTime();
    stopTime("time_line_id");
}

The test output looks like follows:

Starting TimeWatcher:
Starting TimeWatcher: time_line_id
TimeWatcher time_line_id after 3500 milliseconds
TimeWatcher after 8500 seconds
TimeWatcher time_line_id after 8500 milliseconds
Time line ids should not exist as test variables before the action is called for the first time. This would break the time line initialization.
In case no time line id is specified the framework will measure the time for a default time line. To print out the current elapsed time for a time line you simply have to place the `<trace-time> action into the action chain again and again, using the respective time line identifier. The elapsed time will be printed out to the console every time.

Each time line is stored as test variable in the test case. By default you will have the following test variables set for each time line:

CITRUS_TIMELINE

first timestamp of time line

CITRUS_TIMELINE_VALUE

latest time measurement value (time passed since first timestamp in milliseconds)

According to your time line id you will get different test variable names. Also you can customize the time value suffix (default: _VALUE):

XML DSL
<trace-time id="custom_watcher" suffix="_1st"/>
<sleep/>
<trace-time id="custom_watcher" suffix="_2nd"/>
Java DSL
@CitrusTest
stopTime("custom_watcher", "_1st");
sleep();
stopTime("custom_watcher", "_2nd");

You will get following test variables set:

custom_watcher

first timestamp of time line

custom_watcher_1st

time passed since start

custom_watcher_2nd

time passed since start

Of course using the same suffix multiple times will overwrite the timestamps in test variables.

8.9. Create variables

As you know variables usually are defined at the beginning of the test case (test-variables). It might also be helpful to reset existing variables as well as to define new variables during the test. The action <create-variables> is able to declare new variables or overwrite existing ones.

XML DSL
<testcase name="createVariablesTest">
    <variables>
        <variable name="myVariable" value="12345"/>
        <variable name="id" value="54321"/>
    </variables>
    <actions>
        <echo>
            <message>Current variable value: ${myVariable}</message>
        </echo>

        <create-variables>
            <variable name="myVariable" value="${id}"/>
            <variable name="newVariable" value="'this is a test'"/>
        </create-variables>

        <echo>
            <message>Current variable value: ${myVariable} </message>
        </echo>

        <echo>
            <message>
              New variable 'newVariable' has the value: ${newVariable}
            </message>
        </echo>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void createVariableTest() {
    variable("myVariable", "12345");
    variable("id", "54321");

    echo("Current variable value: ${myVariable}");

    createVariable("myVariable", "${id}");
    createVariable("newVariable", "this is a test");

    echo("Current variable value: ${myVariable}");

    echo("New variable 'newVariable' has the value: ${newVariable}");
}
Please note the difference between the variable() method and the createVariable() method. The first initializes the test case with the test variables. So all variables defined with this method are valid from the very beginning of the test. In contrary to that the createVariable() is executed within the test action chain. The newly created variables are then valid for the rest of the test. Trailing actions can reference the variables as usual with the variable expression.

8.10. Trace variables

You already know the <echo> action that prints messages to the console or logger. The <trace-variables> action is specially designed to trace all currently valid test variables to the console. This was mainly used by us for debug reasons. The usage is quite simple:

XML DSL
<testcase name="traceVariablesTest">
    <variables>
        <variable name="myVariable" value="12345"/>
        <variable name="nextVariable" value="54321"/>
    </variables>
    <actions>
        <trace-variables>
            <variable name="myVariable"/>
            <variable name="nextVariable"/>
        </trace-variables>

        <trace-variables/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void traceTest() {
    variable("myVariable", "12345");
    variable("nextVariable", "54321");

    traceVariables("myVariable", "nextVariable");
    traceVariables();
}

Simply add the <trace-variables> action to your action chain and all variables will be printed out to the console. You are able to define a special set of variables by using the <variable> child elements. See the output that was generated by the test example above:

Current value of variable myVariable = 12345
Current value of variable nextVariable = 54321

8.11. Transform

The `<transform>` action transforms XML fragments with XSLT in order to construct various XML representations. The transformation result is stored into a test variable for further usage. The property xml-data defines the XML source, that is going to be transformed, while xslt-data defines the XSLT transformation rules. The attribute variable specifies the target test variable which receives the transformation result. The tester might use the action to transform XML messages as shown in the next code example:

XML DSL
  <testcase name="transformTest">
      <actions>
          <transform variable="result">
              <xml-data>
                  <![CDATA[
                      <TestRequest>
                          <Message>Hello World!</Message>
                      </TestRequest>
                  ]]>
              </xml-data>
              <xslt-data>
                  <![CDATA[
                      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                      <xsl:template match="/">
                          <html>
                              <body>
                                  <h2>Test Request</h2>
                                  <p>Message: <xsl:value-of select="TestRequest/Message"/></p>
                              </body>
                          </html>
                      </xsl:template>
                      </xsl:stylesheet>
                  ]]>
              </xslt-data>
          </transform>
          <echo>
              <message>${result}</message>
          </echo>
      </actions>
  </testcase>

The transformation above results to:

  <html>
      <body>
          <h2>Test Request</h2>
          <p>Message: Hello World!</p>
      </body>
  </html>

In the example we used CDATA sections to define the transformation source as well as the XSL transformation rules. As usual you can also use external file resources here. The transform action with external file resources looks like follows:

  <transform variable="result">
      <xml-resource file="classpath:transform-source.xml"/>
      <xslt-resource file="classpath:transform.xslt"/>
  </transform>

The Java DSL alternative for transforming data via XSTL in Citrus looks like follows:

Java DSL designer
@CitrusTest
public void transformTest() {
    transform()
        .source("<TestRequest>" +
                    "<Message>Hello World!</Message>" +
                "</TestRequest>")
        .xslt("<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n" +
                    "<xsl:template match=\"/\">\n" +
                    "<html>\n" +
                        "<body>\n" +
                            "<h2>Test Request</h2>\n" +
                            "<p>Message: <xsl:value-of select=\"TestRequest/Message\"/></p>\n" +
                        "</body>\n" +
                    "</html>\n" +
                    "</xsl:template>\n" +
                "</xsl:stylesheet>")
        .result("result");

    echo("${result}");

    transform()
        .source(new ClassPathResource("com/consol/citrus/actions/transform-source.xml"))
        .xslt(new ClassPathResource("com/consol/citrus/actions/transform.xslt"))
        .result("result");

    echo("${result}");
}
Java DSL runner
@CitrusTest
public void transformTest() {
    transform(action ->
        action.source("<TestRequest>" +
                        "<Message>Hello World!</Message>" +
                    "</TestRequest>")
        .xslt("<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n" +
                "<xsl:template match=\"/\">\n" +
                "<html>\n" +
                    "<body>\n" +
                        "<h2>Test Request</h2>\n" +
                        "<p>Message: <xsl:value-of select=\"TestRequest/Message\"/></p>\n" +
                    "</body>\n" +
                "</html>\n" +
                "</xsl:template>\n" +
            "</xsl:stylesheet>")
        .result("result"));

    echo("${result}");

    transform(action ->
        action.source(new ClassPathResource("com/consol/citrus/actions/transform-source.xml"))
              .xslt(new ClassPathResource("com/consol/citrus/actions/transform.xslt"))
              .result("result"));

    echo("${result}");
}

Defining multi-line Strings with nested quotes is no fun in Java. So you may want to use external file resources for your scripts as shown in the second part of the example. In fact you could also use script languages like Groovy or Scala that have much better support for multi-line Strings.

8.12. Groovy script execution

Groovy is an agile dynamic language for the Java Platform. Groovy ships with a lot of very powerful features and fits perfectly with Java as it is based on Java and runs inside the JVM.

The Groovy test action support lives in a separate module. You need to add the module to your project to use the functionality.
citrus-groovy dependency module
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-groovy</artifactId>
  <version>${citrus.version}</version>
</dependency>

The Citrus Groovy support might be the entrance for you to write customized test actions. You can easily execute Groovy code inside a test case, just like a normal test action. The whole test context with all variables is available to the Groovy action. This means someone can change variable values or create new variables very easily.

Let’s have a look at some examples in order to understand the possible Groovy code interactions in Citrus:

XML DSL
<testcase name="groovyTest">
  <variables>
    <variable name="time" value="citrus:currentDate()"/>
  </variables>
  <actions>
    <groovy>
        println 'Hello Citrus'
    </groovy>
    <groovy>
        println 'The variable is: ${time}'
    </groovy>
    <groovy resource="classpath:com/consol/citrus/script/example.groovy"/>
  </actions>
</testcase>
Java DSL designer
@CitrusTest
public void groovyTest() {
    groovy("println 'Hello Citrus'");
    groovy("println 'The variable is: ${time}'");

    groovy(new ClassPathResource("com/consol/citrus/script/example.groovy"));
}
Java DSL runner
@CitrusTest
public void groovyTest() {
    groovy(action -> action.script("println 'Hello Citrus'"));
    groovy(action -> action.script("println 'The variable is: ${time}'"));

    groovy(action -> action.script(new ClassPathResource("com/consol/citrus/script/example.groovy")));
}

As you can see it is possible to write Groovy code directly into the test case. Citrus will interpret and execute the Groovy code at runtime. As usual nested variable expressions are replaced with respective values. In general this is done in advance before the Groovy code is interpreted. For more complex Groovy code sections which grow in lines of code you can also reference external file resources.

After this basic Groovy code usage inside a test case we might be interested accessing the whole TestContext. The TestContext Java object holds all test variables and function definitions for the test case and can be referenced in Groovy code via simple naming convention. Just access the object reference 'context' and you are able to manipulate the TestContext (e.g. setting a new variable which is directly ready for use in following test actions).

XML DSL
<testcase name="groovyTest">
  <actions>
    <groovy>
      context.setVariable("greetingText","Hello Citrus")
      println context.getVariable("greetingText")
    </groovy>
    <echo>
      <message>New variable: ${greetingText}</message>
    </echo>
  </actions>
</testcase>
The implicit TestContext access that was shown in the previous sample works with a default Groovy script template provided by Citrus. The Groovy code you write in the test case is automatically surrounded with a Groovy script which takes care of handling the TestContext. The default template looks like follows:
import com.consol.citrus.*
import com.consol.citrus.variable.*
import com.consol.citrus.context.TestContext
import com.consol.citrus.script.GroovyAction.ScriptExecutor

public class GScript implements ScriptExecutor {
    public void execute(TestContext context) {
        @SCRIPTBODY@
    }
}

Your code is placed in substitution to the @SCRIPTBODY@ placeholder. Now you might understand how Citrus handles the context automatically. You can also write your own script templates making more advanced usage of other Java APIs and Groovy code. Just add a script template path to the test action like this:

<groovy script-template="classpath:my-custom-template.groovy">
  [...]
</groovy>

On the other hand you can disable the automatic script template wrapping in your action at all:

<groovy use-script-template="false">
  println 'Just use some Groovy code'
</groovy>

The next example deals with advanced Groovy code and writing whole classes. We write a new Groovy class which implements the ScriptExecutor interface offered by Citrus. This interface defines a special execute method and provides access to the whole TestContext for advanced test variables access.

<testcase name="groovyTest">
  <variables>
    <variable name="time" value="citrus:currentDate()"/>
  </variables>
  <actions>
    <groovy>
      <![CDATA[
        import com.consol.citrus.*
        import com.consol.citrus.variable.*
        import com.consol.citrus.context.TestContext
        import com.consol.citrus.script.GroovyAction.ScriptExecutor

        public class GScript implements ScriptExecutor {
            public void execute(TestContext context) {
                println context.getVariable("time")
            }
        }
      ]]>
    </groovy>
  </actions>
</testcase>

Implementing the ScriptExecutor interface in a custom Groovy class is applicable for very special test context manipulations as you are able to import and use other Java API classes in this code.

8.13. Failing the test

The fail action will generate an exception in order to terminate the test case with error. The test case will therefore not be successful in the reports.

The user can specify a custom error message for the exception in order to describe the error cause. Here is a very simple example to clarify the syntax:

XML DSL
<testcase name="failTest">
    <actions>
        <fail message="Test will fail with custom message"/>
    </actions>
</testcase>

Test results:

Execution of test: failTest failed! Nested exception is:
com.consol.citrus.exceptions.CitrusRuntimeException:
Test will fail with custom message

[...]

CITRUS TEST RESULTS

failTest          : failed - Exception is: Test will fail with custom message

Found 1 test cases to execute
Skipped 0 test cases (0.0%)
Executed 1 test cases, containing 3 actions
Tests failed:        1 (100.0%)
Tests successfully:  0 (0.0%)

While using the Java DSL tester might want to raise some Java exceptions in the middle of configuring the test case. But this is not possible as we have to separate the design time and the execution time of the test case. The @CitrusTest annotated configuration method is called for building up the whole test case. After this method was processed the test gets executed in runtime oth the test. If you specify a throws exception statement in the configuration method this will not be done at runtime but at design time. This is why you have to use the special fail test action which raises a Java exception during the runtime of the test. The next example will not work as expected:

Java DSL
@CitrusTest
public void wrongUsageSample() {
    // some test actions

    throw new ValidationException("This test should fail now"); // does not work as expected
}

The validation exception above is directly raised before the test is able to start as the @CitrusTest annotated method does not represent the test runtime. Instead of this we have to use the fail action as follows:

Java DSL
@CitrusTest
public void failTest() {
    // some test actions

    fail("This test should fail now"); // fails at test runtime as expected
}

Now the test fails at runtime as the fail action is raised during the test execution as expected.

8.14. Input

During the test case execution it is possible to read some user input from the command line. The test execution will stop and wait for keyboard inputs over the standard input stream. The user has to type the input and end it with the return key.

The user input is stored to the respective variable value.

XML DSL
<testcase name="inputTest">
    <variables>
        <variable name="userinput" value=""></variable>
        <variable name="userinput1" value=""></variable>
        <variable name="userinput2" value="y"></variable>
        <variable name="userinput3" value="yes"></variable>
        <variable name="userinput4" value=""></variable>
    </variables>
    <actions>
        <input/>
        <echo><message>user input was: ${userinput}</message></echo>

        <input message="Now press enter:" variable="userinput1"/>
        <echo><message>user input was: ${userinput1}</message></echo>

        <input message="Do you want to continue?"
                  valid-answers="y/n" variable="userinput2"/>
        <echo><message>user input was: ${userinput2}</message></echo>

        <input message="Do you want to continue?"
                  valid-answers="yes/no" variable="userinput3"/>
        <echo><message>user input was: ${userinput3}</message></echo>

        <input variable="userinput4"/>
        <echo><message>user input was: ${userinput4}</message></echo>
    </actions>
</testcase>

As you can see the input action is customizable with a prompt message that is displayed to the user and some valid answer possibilities. The user input is stored to a test variable for further use in the test case. In detail the input action offers following attributes:

message

message displayed to the user

valid-answers

possible valid answers separated with '/' character

variable

result variable name holding the user input (default = ${userinput})

The same action in Java DSL now looks quite familiar to us although attribute naming is slightly different:

Java DSL designer
@CitrusTest
public void inputActionTest() {
    variable("userinput", "");
    variable("userinput1", "");
    variable("userinput2", "y");
    variable("userinput3", "yes");
    variable("userinput4", "");

    input();
    echo("user input was: ${userinput}");
    input().message("Now press enter:").result("userinput1");
    echo("user input was: ${userinput1}");
    input().message("Do you want to continue?").answers("y", "n").result("userinput2");
    echo("user input was: ${userinput2}");
    input().message("Do you want to continue?").answers("yes", "no").result("userinput3");
    echo("user input was: ${userinput3}");
    input().result("userinput4");
    echo("user input was: ${userinput4}");
}
Java DSL runner
@CitrusTest
public void inputActionTest() {
    variable("userinput", "");
    variable("userinput1", "");
    variable("userinput2", "y");
    variable("userinput3", "yes");
    variable("userinput4", "");

    input(action -> {});
    echo("user input was: ${userinput}");
    input(action -> action.message("Now press enter:").result("userinput1"));
    echo("user input was: ${userinput1}");
    input(action -> action.message("Do you want to continue?").answers("y", "n").result("userinput2"));
    echo("user input was: ${userinput2}");
    input(action -> action.message("Do you want to continue?").answers("yes", "no").result("userinput3"));
    echo("user input was: ${userinput3}");
    input(action -> action.result("userinput4"));
    echo("user input was: ${userinput4}");
}

When the user input is restricted to a set of valid answers the input validation of course can fail due to mismatch. This is the case when the user provides some input not matching the valid answers given. In this case the user is again asked to provide valid input. The test action will continue to ask for valid input until a valid answer is given.

User inputs may not fit to automatic testing in terms of continuous integration testing where no user is present to type in the correct answer over the keyboard. In this case you can always skip the user input in advance by specifying a variable that matches the user input variable name. As the user input variable is then already present the user input is missed out and the test proceeds automatically.

8.15. Load

You are able to load properties from external property files and store them as test variables. The action will require a file resource either from class path or file system in order to read the property values.

Let us look at an example to get an idea about this action:

Content of load.properties
username=Mickey Mouse
greeting.text=Hello Test Framework
XML DSL
<testcase name="loadPropertiesTest">
    <actions>
        <load>
            <properties file="file:tests/resources/load.properties"/>
        </load>

        <trace-variables/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void loadPropertiesTest() {
    load("file:tests/resources/load.properties");

    traceVariables();
}
Output
Current value of variable username = Mickey Mouse
Current value of variable greeting.text = Hello Test Framework

The action will load all available properties in the file load.properties and store them to the test case as local variables.

Please be aware of the fact that existing variables are overwritten!

8.16. Purging JMS destinations

Purging JMS destinations during the test run is quite essential. Different test cases can influence each other when sending messages to the same JMS destinations. A test case should only receive those messages that actually belong to it. Therefore it is a good idea to purge all JMS queue destinations between the test cases. Obsolete messages that are stuck in a JMS queue for some reason are then removed so that the following test case is not offended.

Citrus provides special support for JMS related features. We have to activate those JMS features in our test case by adding a special "jms" namespace and schema definition location to the test case XML.
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase"
        xmlns:spring="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jms="http://www.citrusframework.org/schema/jms/testcase"
        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/schema/jms/testcase
        http://www.citrusframework.org/schema/jms/testcase/citrus-jms-testcase.xsd">

    [...]

</beans>

Now we are ready to use the JMS features in our test case in order to purge some JMS queues. This can be done with following action definition:

XML DSL
<testcase name="purgeTest">
  <actions>
      <jms:purge-jms-queues>
          <jms:queue name="Some.JMS.QUEUE.Name"/>
          <jms:queue name="Another.JMS.QUEUE.Name"/>
          <jms:queue name="My.JMS.QUEUE.Name"/>
      </jms:purge-jms-queues>

      <jms:purge-jms-queues connection-factory="connectionFactory">
          <jms:queue name="Some.JMS.QUEUE.Name"/>
          <jms:queue name="Another.JMS.QUEUE.Name"/>
          <jms:queue name="My.JMS.QUEUE.Name"/>
      </jms:purge-jms-queues>
  </actions>
</testcase>

Notice that we have referenced the jms namespace when using the purge-jms-queues test action.

Java DSL designer
@Autowired
@Qualifier("connectionFactory")
private ConnectionFactory connectionFactory;

@CitrusTest
public void purgeTest() {
    purgeQueues()
        .queue("Some.JMS.QUEUE.Name")
        .queue("Another.JMS.QUEUE.Name");

    purgeQueues(connectionFactory)
        .timeout(150L) // custom timeout in ms
        .queue("Some.JMS.QUEUE.Name")
        .queue("Another.JMS.QUEUE.Name");
}
Java DSL runner
@Autowired
@Qualifier("connectionFactory")
private ConnectionFactory connectionFactory;

@CitrusTest
public void purgeTest() {
    purgeQueues(action ->
        action.queue("Some.JMS.QUEUE.Name")
            .queue("Another.JMS.QUEUE.Name"));

    purgeQueues(action -> action.connectionFactory(connectionFactory)
            .timeout(150L) // custom timeout in ms
            .queue("Some.JMS.QUEUE.Name")
            .queue("Another.JMS.QUEUE.Name"));
}

Purging the JMS queues in every test case is quite exhausting because every test case needs to define a purging action at the very beginning of the test. Fortunately the test suite definition offers tasks to run before, between and after the test cases which should ease up this tasks a lot. The test suite offers a very simple way to purge the destinations between the tests. See testsuite-before-testfor more information about this.

As you can see in the next example it is quite easy to specify a group of destinations in the Spring configuration that get purged before a test is executed.

<citrus:before-test id="purgeBeforeTest">
    <citrus:actions>
        <jms:purge-jms-queues>
            <jms:queue name="Some.JMS.QUEUE.Name"/>
            <jms:queue name="Another.JMS.QUEUE.Name"/>
        </jms:purge-jms-queues>
    </citrus:actions>
</citrus:before-test>
Please keep in mind that the JMS related configuration components in Citrus belong to a separate XML namespace jms: . We have to add this namespace declaration to each test case XML and Spring bean XML configuration file as described at the very beginning of this section.

The syntax for purging the destinations is the same as we used it inside the test case. So now we are able to purge JMS destinations with given destination names. But sometimes we do not want to rely on queue or topic names as we retrieve destinations over JNDI for instance. We can deal with destinations coming from JNDI lookup like follows:

<jee:jndi-lookup id="jmsQueueHelloRequestIn" jndi-name="jms/jmsQueueHelloRequestIn"/>
<jee:jndi-lookup id="jmsQueueHelloResponseOut" jndi-name="jms/jmsQueueHelloResponseOut"/>

<citrus:before-test id="purgeBeforeTest">
    <citrus:actions>
        <jms:purge-jms-queues>
            <jms:queue ref="jmsQueueHelloRequestIn"/>
            <jms:queue ref="jmsQueueHelloResponseOut"/>
        </jms:purge-jms-queues>
    </citrus:actions>
</citrus:before-test>

We just use the attribute 'ref' instead of 'name' and Citrus is looking for a bean reference for that identifier that resolves to a JMS destination. You can use the JNDI bean references inside a test case, too.

XML DSL
<testcase name="purgeTest">
  <actions>
      <jms:purge-jms-queues>
          <jms:queue ref="jmsQueueHelloRequestIn"/>
          <jms:queue ref="jmsQueueHelloResponseOut"/>
      </jms:purge-jms-queues>
  </actions>
</testcase>

Of course you can use queue object references also in Java DSL test cases. Here we easily can use Spring’s dependency injection with autowiring to get the object references from the IoC container.

Java DSL designer
@Autowired
@Qualifier("jmsQueueHelloRequestIn")
private Queue jmsQueueHelloRequestIn;

@Autowired
@Qualifier("jmsQueueHelloResponseOut")
private Queue jmsQueueHelloResponseOut;

@CitrusTest
public void purgeTest() {
    purgeQueues()
        .queue(jmsQueueHelloRequestIn)
        .queue(jmsQueueHelloResponseOut);
}
Java DSL runner
@Autowired
@Qualifier("jmsQueueHelloRequestIn")
private Queue jmsQueueHelloRequestIn;

@Autowired
@Qualifier("jmsQueueHelloResponseOut")
private Queue jmsQueueHelloResponseOut;

@CitrusTest
public void purgeTest() {
    purgeQueues(action ->
        action.queue(jmsQueueHelloRequestIn)
            .queue(jmsQueueHelloResponseOut));
}
You can mix queue name and queue object references as you like within one single purge queue test action.

8.17. Purging message channels

Message channels define central messaging destinations in Citrus. These are namely in memory message queues holding messages for test cases. These messages may become obsolete during a test run, especially when test cases fail and stop in their message consumption. Purging these message channel destinations is essential in these scenarios in order to not influence upcoming test cases. Each test case should only receive those messages that actually refer to the test model. Therefore it is a good idea to purge all message channel destinations between the test cases. Obsolete messages that get stuck in a message channel destination for some reason are then removed so that upcoming test case are not broken.

Following action definition purges all messages from a list of message channels:

XML DSL
<testcase name="purgeChannelTest">
  <actions>
      <purge-channel>
          <channel name="someChannelName"/>
          <channel name="anotherChannelName"/>
      </purge-channel>

      <purge-channel>
          <channel ref="someChannel"/>
          <channel ref="anotherChannel"/>
      </purge-channel>
  </actions>
</testcase>

As you can see the test action supports channel names as well as channel references to Spring bean instances. When using channel references you refer to the Spring bean id or name in your application context.

The Java DSL works quite similar as you can read from next examples:

Java DSL designer
@Autowired
@Qualifier("channelResolver")
private DestinationResolver<MessageChannel> channelResolver;

@CitrusTest
public void purgeTest() {
    purgeChannels()
        .channelResolver(channelResolver)
        .channelNames("ch1", "ch2", "ch3")
        .channel("ch4");
}
Java DSL runner
@Autowired
@Qualifier("channelResolver")
private DestinationResolver<MessageChannel> channelResolver;

@CitrusTest
public void purgeTest() {
    purgeChannels(action ->
        action.channelResolver(channelResolver)
                .channelNames("ch1", "ch2", "ch3")
                .channel("ch4"));
}

The channel resolver reference is optional. By default Citrus will automatically use a Spring application context channel resolver so you just have to use the respective Spring bean names that are configured in the Spring application context. However setting a custom channel resolver may be adequate for you in some special cases.

While speaking of Spring application context bean references the next example uses such bean references for channels to purge.

Java DSL designer
@Autowired
@Qualifier("channel1")
private MessageChannel channel1;

@Autowired
@Qualifier("channel2")
private MessageChannel channel2;

@Autowired
@Qualifier("channel3")
private MessageChannel channel3;

@CitrusTest
public void purgeTest() {
    purgeChannels()
        .channels(channel1, channel2)
        .channel(channel3);
}
Java DSL runner
@Autowired
@Qualifier("channel1")
private MessageChannel channel1;

@Autowired
@Qualifier("channel2")
private MessageChannel channel2;

@Autowired
@Qualifier("channel3")
private MessageChannel channel3;

@CitrusTest
public void purgeTest() {
    purgeChannels(action ->
        action.channels(channel1, channel2)
                .channel(channel3));
}

Message selectors enable you to selectively remove messages from the destination. All messages that pass the message selection logic get deleted the other messages will remain unchanged inside the channel destination. The message selector is a Spring bean that implements a special message selector interface. A possible implementation could be a selector deleting all messages that are older than five seconds:

import org.springframework.messaging.Message;
import org.springframework.integration.core.MessageSelector;

public class TimeBasedMessageSelector implements MessageSelector {

    public boolean accept(Message<?> message) {
        if (System.currentTimeMillis() - message.getHeaders().getTimestamp() > 5000) {
            return false;
        } else {
            return true;
        }
    }

}
The message selector returns false for those messages that should be deleted from the channel!

You simply define the message selector as a new Spring bean in the Citrus application context and reference it in your test action property.

<bean id="specialMessageSelector"
    class="com.consol.citrus.special.TimeBasedMessageSelector"/>

Now let us have a look at how you reference the selector in your test case:

XML DSL
<purge-channel message-selector="specialMessageSelector">
  <channel name="someChannelName"/>
  <channel name="anotherChannelName"/>
</purge-channel>
Java DSL designer
@Autowired
@Qualifier("specialMessageSelector")
private MessageSelector specialMessageSelector;

@CitrusTest
public void purgeTest() {
    purgeChannels()
        .channelNames("ch1", "ch2", "ch3")
        .selector(specialMessageSelector);
}
Java DSL runner
@Autowired
@Qualifier("specialMessageSelector")
private MessageSelector specialMessageSelector;

@CitrusTest
public void purgeTest() {
    purgeChannels(action ->
        action.channelNames("ch1", "ch2", "ch3")
                .selector(specialMessageSelector));
}

In the examples above we use a message selector implementation that gets injected via Spring IoC container.

Purging channels in each test case every time is quite exhausting because every test case needs to define a purging action at the very beginning of the test. A more straight forward approach would be to introduce some purging action which is automatically executed before each test. Fortunately the Citrus test suite offers a very simple way to do this. It is described in testsuite-before-test.

When using the special action sequence before test cases we are able to purge channel destinations every time a test case executes. See the upcoming example to find out how the action is defined in the Spring configuration application context.

<citrus:before-test id="purgeBeforeTest">
    <citrus:actions>
        <purge-channel>
            <channel name="fooChannel"/>
            <channel name="barChannel"/>
        </purge-channel>
    </citrus:actions>
</citrus:before-test>

Just use this before-test bean in the Spring bean application context and the purge channel action is active. Obsolete messages that are waiting on the message channels for consumption are purged before the next test in line is executed.

Purging message channels becomes also very interesting when working with server instances in Citrus. Each server component automatically has an inbound message channel where incoming messages are stored to internally. So if you need to clean up a server that has already stored some incoming messages you can do this easily by purging the internal message channel. The message channel follows a naming convention {serverName}.inbound where {serverName} is the Spring bean name of the Citrus server endpoint component. If you purge this internal channel in a before test nature you are sure that obsolete messages on a server instance get purged before each test is executed.

8.18. Purging endpoints

Citrus works with message endpoints when sending and receiving messages. In general endpoints can also queue messages. This is especially the case when using JMS message endpoints or any server endpoint component in Citrus. These are in memory message queues holding messages for test cases. These messages may become obsolete during a test run, especially when a test case that would consume the messages fails. Deleting all messages from a message endpoint is therefore a useful task and is essential in such scenarios so that upcoming test cases are not influenced. Each test case should only receive those messages that actually refer to the test model. Therefore it is a good idea to purge all message endpoint destinations between the test cases. Obsolete messages that get stuck in a message endpoint destination for some reason are then removed so that upcoming test case are not broken.

Following action definition purges all messages from a list of message endpoints:

XML DSL
<testcase name="purgeEndpointTest">
  <actions>
      <purge-endpoint>
          <endpoint name="someEndpointName"/>
          <endpoint name="anotherEndpointName"/>
      </purge-endpoint>

      <purge-endpoint>
          <endpoint ref="someEndpoint"/>
          <endpoint ref="anotherEndpoint"/>
      </purge-endpoint>
  </actions>
</testcase>

As you can see the test action supports endpoint names as well as endpoint references to Spring bean instances. When using endpoint references you refer to the Spring bean name in your application context.

The Java DSL works quite similar - have a look:

Java DSL designer
@CitrusTest
public void purgeTest() {
    purgeEndpoints()
        .endpointNames("endpoint1", "endpoint2", "endpoint3")
        .endpoint("endpoint4");
}
Java DSL runner
@CitrusTest
public void purgeTest() {
    purgeEndpoints(action ->
        action.endpointNames("endpoint1", "endpoint2", "endpoint3")
                .endpoint("endpoint4"));
}

When using the Java DSL we can inject endpoint objects with Spring bean container IoC. The next example uses such bean references for endpoints in a purge action.

Java DSL designer
@Autowired
@Qualifier("endpoint1")
private Endpoint endpoint1;

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

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

@CitrusTest
public void purgeTest() {
    purgeEndpoints()
        .endpoints(endpoint1, endpoint2)
        .endpoint(endpoint3);
}
Java DSL runner
@Autowired
@Qualifier("endpoint1")
private Endpoint endpoint1;

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

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

@CitrusTest
public void purgeTest() {
    purgeEndpoints(action ->
        action.endpoints(endpoint1, endpoint2)
                .endpoint(endpoint3));
}

Message selectors enable you to selectively remove messages from an endpoint. All messages that meet the message selector condition get deleted and the other messages remain inside the endpoint destination. The message selector is either a normal String name-value representation or a map of key value pairs:

XML DSL
<purge-endpoints>
  <selector>
    <value>operation = 'sayHello'</value>
  </selector>
  <endpoint name="someEndpointName"/>
  <endpoint name="anotherEndpointName"/>
</purge-endpoints>
Java DSL designer
@CitrusTest
public void purgeTest() {
    purgeEndpoints()
        .endpointNames("endpoint1", "endpoint2", "endpoint3")
        .selector("operation = 'sayHello'");
}
Java DSL runner
@CitrusTest
public void purgeTest() {
    purgeEndpoints(action ->
        action.endpointNames("endpoint1", "endpoint2", "endpoint3")
                .selector("operation = 'sayHello'"));
}

In the examples above we use a String to represent the message selector expression. In general the message selector operates on the message header. So following on from that we remove all messages selectively that have a message header operation with its value sayHello .

Purging endpoints in each test case every time is quite exhausting because every test case needs to define a purging action at the very beginning of the test. A more straight forward approach would be to introduce some purging action which is automatically executed before each test. Fortunately the Citrus test suite offers a very simple way to do this. It is described in testsuite-before-test.

When using the special action sequence before test cases we are able to purge endpoint destinations every time a test case executes. See the upcoming example to find out how the action is defined in the Spring configuration application context.

<citrus:before-test id="purgeBeforeTest">
    <citrus:actions>
        <purge-endpoint>
            <endpoint name="fooEndpoint"/>
            <endpoint name="barEndpoint"/>
        </purge-endpoint>
    </citrus:actions>
</citrus:before-test>

Just use this before-test bean in the Spring bean application context and the purge endpoint action is active. Obsolete messages that are waiting on the message endpoints for consumption are purged before the next test in line is executed.

Purging message endpoints becomes also very interesting when working with server instances in Citrus. Each server component automatically has an inbound message endpoint where incoming messages are stored to internally. Citrus will automatically use this incoming message endpoint as target for the purge action so you can just use the server instance as you know it from your configuration in any purge action.

8.19. Assert failure

Citrus test actions fail with Java exceptions and error messages. This gives you the opportunity to expect an action to fail during test execution. You can simple assert a Java exception to be thrown during execution. See the example for an assert action definition in a test case:

XML DSL
<testcase name="assertFailureTest">
    <actions>
        <assert exception="com.consol.citrus.exceptions.CitrusRuntimeException"
                   message="Unknown variable ${date}">
            <when>
                <echo>
                    <message>Current date is: ${date}</message>
                </echo>
            </when>
        </assert>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void assertTest() {
    assertException().exception(com.consol.citrus.exceptions.CitrusRuntimeException.class)
                     .message("Unknown variable ${date}")
                .when(echo("Current date is: ${date}"));
}
Note that the assert action requires an exception. In case no exception is thrown by the embedded test action the assertion and the test case will fail!

The assert action always wraps a single test action, which is then monitored for failure. In case the nested test action fails with error you can validate the error in its type and error message (optional). The failure has to fit the expected one exactly otherwise the assertion fails itself.

Important to notice is the fact that asserted exceptions do not cause failure of the test case. As you except the failure to happen the test continues with its work once the assertion is done successfully.

8.20. Catch exceptions

In the previous chapter we have seen how to expect failures in Citrus with assert action. Now the assert action is designed for single actions to be monitored and for failures to be expected in any case. The 'catch' action in contrary can hold several nested test actions and exception failure is optional.

The nested actions are error proof for the chosen exception type. This means possible exceptions are caught and ignored - the test case will not fail for this exception type. But only for this particular exception type! Other exception types that occur during execution do cause the test to fail as usual.

XML DSL
<testcase name="catchExceptionTest">
    <actions>
        <catch exception="com.consol.citrus.exceptions.CitrusRuntimeException">
            <echo>
                <message>Current date is: ${date}</message>
            </echo>
        </catch>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void catchTest() {
    catchException().exception(CitrusRuntimeException.class)
                    .when(echo("Current date is: ${date}"));
}
Note that there is no validation available in a catch block. So catching exceptions is just to make a test more stable towards errors that can occur. The caught exception does not cause any failure in the test. The test case may continue with execution as if there was not failure. Also notice that the catch action is also happy when no exception at all is raised. In contrary to that the assert action requires the exception and an assert action is failing in positive processing.

Catching exceptions like this may only fit to very error prone action blocks where failures do not harm the test case success. Otherwise a failure in a test action should always reflect to the whole test case to fail with errors.

Java developers might ask why not use try-catch Java block instead? The answer is simple yet very important to understand. The test method is called by the Java DSL test case builder for building the Citrus test. This can be referred to as the design time of the test. After the building test method was processed the test gets executed, which can be called the runtime of the test. This means that a try-catch block within the design time method will never perform during the test run. The only reliable way to add the catch capability to the test as part of the test case runtime is to use the Citrus test action which gets executed during test runtime.

8.21. Apache Ant build

The <ant> action loads a build.xml Ant file and executes one or more targets in the Ant project. The target is executed with optional build properties passed to the Ant run. The Ant build output is logged with Citrus logger and the test case success is bound to the Ant build success. This means in case the Ant build fails for some reason the test case will also fail with build exception accordingly.

See this basic Ant run example to see how it works within your test case:

XML DSL
<testcase name="AntRunTest">
    <variables>
        <variable name="today" value="citrus:currentDate()"/>
    </variables>
    <actions>
        <ant build-file="classpath:com/consol/citrus/actions/build.xml">
            <execute target="sayHello"/>
            <properties>
                <property name="date" value="${today}"/>
                <property name="welcomeText" value="Hello!"/>
            </properties>
        </ant>
    </actions>
</testcase>
Java DSL designer
@CitrusTest
public void antRunTest() {
    variable("today", "citrus:currentDate()");

    antrun("classpath:com/consol/citrus/actions/build.xml")
        .target("sayHello")
        .property("date", "${today}")
        .property("welcomeText", "$Hello!");
}
Java DSL runner
@CitrusTest
public void antRunTest() {
    variable("today", "citrus:currentDate()");

    antrun(action -> action.buildFilePath("classpath:com/consol/citrus/actions/build.xml")
                .target("sayHello")
                .property("date", "${today}")
                .property("welcomeText", "$Hello!"));
}

The respective build.xml Ant file must provide the target to call. For example:

<project name="citrus-build" default="sayHello">
    <property name="welcomeText" value="Welcome to Citrus!"></property>

    <target name="sayHello">
        <echo message="${welcomeText} - Today is ${date}"></echo>
    </target>

    <target name="sayGoodbye">
        <echo message="Goodbye everybody!"></echo>
    </target>
</project>

As you can see you can pass custom build properties to the Ant build execution. Existing Ant build properties are replaced and you can use the properties in your build file as usual.

You can also call multiple targets within one single build run by using a comma separated list of target names:

XML DSL
<testcase name="AntRunTest">
    <variables>
        <variable name="today" value="citrus:currentDate()"/>
    </variables>
    <actions>
        <ant build-file="classpath:com/consol/citrus/actions/build.xml">
            <execute targets="sayHello,sayGoodbye"/>
            <properties>
                <property name="date" value="${today}"/>
            </properties>
        </ant>
    </actions>
</testcase>
Java DSL designer
@CitrusTest
public void antRunTest() {
    variable("today", "citrus:currentDate()");

    antrun("classpath:com/consol/citrus/actions/build.xml")
        .targets("sayHello", "sayGoodbye")
        .property("date", "${today}");
}
Java DSL runner
@CitrusTest
public void antRunTest() {
    variable("today", "citrus:currentDate()");

    antrun(action -> action.buildFilePath("classpath:com/consol/citrus/actions/build.xml")
                .targets("sayHello", "sayGoodbye")
                .property("date", "${today}"));
}

The build properties can live in external file resource as an alternative to the inline property definitions. You just have to use the respective file resource path and all nested properties get loaded as build properties.

In addition to that you can also define a custom build listener. The build listener must implement the Ant API interface org.apache.tools.ant.BuildListener . During the Ant build run the build listener is called with several callback methods (e.g. buildStarted(), buildFinished(), targetStarted(), targetFinished(), …). This is how you can add additional logic to the Ant build run from Citrus. A custom build listener could manage the fail state of your test case, in particular by raising some exception forcing the test case to fail accordingly.

XML DSL
<testcase name="AntRunTest">
    <actions>
        <ant build-file="classpath:com/consol/citrus/actions/build.xml"
                build-listener="customBuildListener">
            <execute target="sayHello"/>
            <properties file="classpath:com/consol/citrus/actions/build.properties"/>
        </ant>
    </actions>
</testcase>
Java DSL designer
@Autowired
private BuildListener customBuildListener;

@CitrusTest
public void antRunTest() {
    antrun("classpath:com/consol/citrus/actions/build.xml")
        .target("sayHello")
        .propertyFile("classpath:com/consol/citrus/actions/build.properties")
        .listener(customBuildListener);
}
Java DSL runner
@Autowired
private BuildListener customBuildListener;

@CitrusTest
public void antRunTest() {
    antrun(action -> action.buildFilePath("classpath:com/consol/citrus/actions/build.xml")
            .target("sayHello")
            .propertyFile("classpath:com/consol/citrus/actions/build.properties")
            .listener(customBuildListener));
}

The customBuildListener used in the example above should reference a Spring bean in the Citrus application context. The bean implements the interface org.apache.tools.ant.BuildListener and controls the Ant build run.

8.22. Start/Stop server

Citrus is working with server components that are started and stopped within a test run. This can be a Http server or some SMTP mail server for instance. Usually the Citrus server components are automatically started when Citrus is starting and respectively stopped when Citrus is shutting down. Sometimes it might be helpful to explicitly start and stop a server instance within your test case. Here you can use special start and stop test actions inside your test. This is a good way to test downtime scenarios of interface partners with respective error handling when connections to servers are lost

Let me explain with a simple sample test case:

XML DSL
<testcase name="sleepTest">
    <actions>
        <start server="myMailServer"/>

        <sleep/>

        <stop server="myMailServer"/>
    </actions>
</testcase>

The start and stop server test action receive a server name which references a Spring bean component of type com.consol.citrus.server.Server in your basic Spring application context. The server instance is started or stopped within the test case. As you can see in the next listing we can also start and stop multiple server instances within a single test action.

<testcase name="sleepTest">
    <actions>
        <start>
            <servers>
                <server name="myMailServer"/>
                <server name="myFtpServer"/>
            </servers>
        </start>

        <sleep/>

        <stop>
            <servers>
                <server name="myMailServer"/>
                <server name="myFtpServer"/>
            </servers>
        </stop>
    </actions>
</testcase>

When using the Java DSL the best way to reference a server instance is to autowire the Spring bean via dependency injection. The Spring framework takes case on injecting the proper Spring bean component defined in the Spring application context. This way you can easily start and stop server instances within Java DSL test cases.

Java DSL
@Autowired
@Qualifier("myFtpServer")
private FtpServer myFtpServer;

@CitrusTest
public void startStopServerTest() {
    start(myFtpServer);

    sleep();

    stop(myFtpServer);
}
Starting and stopping server instances is a synchronous test action. This means that your test case is waiting for the server to start before other test actions take place. Startup times and shut down of server instances may delay your test accordingly.

As you can see starting and stopping Citrus server instances is very easy. You can also write your own server implementations by implementing the interface com.consol.citrus.server.Server . All custom server implementations can then be started and stopped during a test case.

8.23. Timer

The <stop-timer> action can be used for stopping either a specific timer (containers-timer) or all timers running within a test. This action is useful when timers are started in the background (using parallel or fork=true) and you wish to stop these timers at the end of the test. Some examples of using this action are provided below:

XML DSL
<testcase name="timerTest">
    <actions>
      <timer id="forkedTimer" fork="true">
        <sleep milliseconds="50" />
      </timer>

      <timer fork="true">
        <sleep milliseconds="50" />
      </timer>

      <timer repeatCount="5">
        <sleep milliseconds="50" />
      </timer>

      <stop-timer timerId="forkedTimer" />
    </actions>
    <finally>
      <stop-timer />
    </finally>
  </testcase>
Java DSL
@CitrusTest
  public void timerTest() {

    timer()
      .timerId("forkedTimer")
      .fork(true)
      .actions(sleep(50L)
    );

    timer()
      .fork(true)
      .actions(sleep(50L)
    );

    timer()
      .repeatCount(5)
      .actions(sleep(50L));

    stopTimer("forkedTimer")

    doFinally().actions(
      stopTimer()
    );
    }

In the above example 3 timers are started, the first 2 in the background and the third in the test execution thread. Timer #3 has a repeatCount set to 5 so it will terminate automatically after 5 runs. Timer #1 and #2 however have no repeatCount set so they will execute until they are told to stop.

Timer #1 is stopped explicitly using the first stopTimer action. Here the stopTimer action includes the name of the timer to stop. This is convenient when you wish to terminate a specific timer. However since no timerId was set for timer #2, you can terminate this (and all other timers) using the 'stopTimer' action with no explicit timerId set.

8.24. Custom action

Now we have a look at the opportunity to add custom test actions to the test case flow. Let us start this section with an example:

XML DSL
<testcase name="ActionReferenceTest">
    <actions>
        <action reference="cleanUpDatabase"/>
        <action reference="mySpecialAction"/>
    </actions>
</testcase>

The generic <action> element references Spring beans that implement the Java interface com.consol.citrus.TestAction . This is a very fast way to add your own action implementations to a Citrus test case. This way you can easily implement your own actions in Java and include them into the test case.

In the example above the called actions are special database cleanup implementations. The actions are defined as Spring beans in the Citrus configuration and get referenced by their bean name or id.

<bean id="cleanUpDatabase" class="my.domain.citrus.actions.SpecialDatabaseCleanupAction">
    <property name="dataSource" ref="testDataSource"/>
</bean>

The Spring application context holds your custom bean implementations. You can set properties and use the full Spring power while implementing your custom test action in Java. Let us have a look on how such a Java class may look like.

import com.consol.citrus.actions.AbstractTestAction;
import com.consol.citrus.context.TestContext;

public class SpecialDatabaseCleanupAction extends AbstractTestAction {

    @Autowired
    private DataSource dataSource;

    @Override
    public void doExecute(TestContext context) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

        jdbcTemplate.execute("...");
    }

}

All you need to do in your Java class is to implement the Citrus com.consol.citrus.TestAction interface. The abstract class com.consol.citrus.actions.AbstractTestAction may help you to start with your custom test action implementation as it provides basic method implementations so you just have to implement the doExecute() method.

When using the Java test case DSL you are also quite comfortable with including your custom test actions.

Java DSL
@Autowired
private SpecialDatabaseCleanupAction cleanUpDatabaseAction;

@CitrusTest
public void genericActionTest() {
    echo("Now let's include our special test action");

    action(cleanUpDatabaseAction);

    echo("That's it!");
}

Using anonymous class implementations is also possible.

Java DSL
@CitrusTest
public void genericActionTest() {
    echo("Now let's call our special test action anonymously");

    action(new AbstractTestAction() {
        public void doExecute(TestContext context) {
            // do something
        }
    });

    echo("That's it!");
}

9. Containers

Similar to templates a container element holds one to many test actions. In contrast to the template the container appears directly inside the test case action chain, meaning that the container is not referenced by more than one test case.

Containers execute the embedded test actions in specific logic. This can be an execution in iteration for instance. Combine different containers with each other and you will be able to generate very powerful hierarchical structures in order to create a complex execution logic. In the following sections some predefined containers are described.

9.1. Sequential

The sequential container executes the embedded test actions in strict sequence. Readers now might search for the difference to the normal action chain that is specified inside the test case. The actual power of sequential containers does show only in combination with other containers like iterations and parallels. We will see this later when handling these containers.

For now the sequential container seems not very sensational - one might say boring - because it simply groups a pair of test actions to sequential execution.

XML DSL
<testcase name="sequentialTest">
    <actions>
        <sequential>
            <trace-time/>
            <sleep/>
            <echo>
                <message>Hallo TestFramework</message>
            </echo>
            <trace-time/>
        </sequential>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void sequentialTest() {
    sequential()
        .actions(
            stopTime(),
            sleep(1.0),
            echo("Hello Citrus"),
            stopTime()
        );
}

9.2. Conditional

Now we deal with conditional executions of test actions. Nested actions inside a conditional container are executed only in case a boolean expression evaluates to true. Otherwise the container execution is not performed at all.

See some example to find out how it works with the conditional expression string.

XML DSL
<testcase name="conditionalTest">
    <variables>
      <variable name="index" value="5"/>
      <variable name="shouldSleep" value="true"/>
    </variables>

    <actions>
        <conditional expression="${index} = 5">
            <sleep seconds="10"/>
        </conditional>

        <conditional expression="${shouldSleep}">
            <sleep seconds="10"/>
        </conditional>

        <conditional expression="@assertThat('${shouldSleep}', 'anyOf(is(true), isEmptyString())')@">
            <sleep seconds="10"/>
        </conditional>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void conditionalTest() {
    variable("index", 5);
    variable("shouldSleep", true);

    conditional().when("${index} = 5"))
        .actions(
            sleep(10000L)
        );

    conditional().when("${shouldSleep}"))
        .actions(
            sleep(10000L)
        );

    conditional().when("${shouldSleep}", anyOf(is("true"), isEmptyString()))
        .actions(
            sleep(10000L)
        );
}

The nested sleep action is executed in case the variable ${index} is equal to the value '5'. This conditional execution of test actions is useful when dealing with different test environments such as different operating systems for instance. The conditional container also supports expressions that evaluate to the character sequence "true" or "false" as shown in the ${shouldSleep} example.

The last conditional container in the example above makes use of Hamcrest matchers. The matcher evaluates to true of false and based on that the container actions are executed or skipped. The Hamcrest matchers are very powerful when it comes to evaluation of multiple conditions at a time.

9.3. Parallel

Parallel containers execute embedded test actions concurrently to each other. Every action in this container will be executed in a separate Java thread. The following example should clarify the usage:

XML DSL
<testcase name="parallelTest">
    <actions>
        <parallel>
            <sleep/>

            <sequential>
                <sleep/>
                <echo>
                    <message>1</message>
                </echo>
            </sequential>

            <echo>
                <message>2</message>
            </echo>

            <echo>
                <message>3</message>
            </echo>

            <iterate condition="i lt= 5"
                        index="i">
                <echo>
                    <message>10</message>
                </echo>
            </iterate>
        </parallel>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void paralletTest() {
    parallel().actions(
        sleep(),
        sequential().actions(
            sleep(),
            echo("1")
        ),
        echo("2"),
        echo("3"),
        iterate().condition("i lt= 5").index("i"))
            .actions(
                echo("10")
            )
    );
}

By default, test actions are processed and executed one action after another. Since the first action is a sleep of five seconds, the whole test would stop and wait for 5 seconds. Things are different inside the parallel container. Here, the descending test actions will not wait, but execute at the same time.

If you are using this container to send or receive messages, you have to use the unique correlation ID of the message to link the actions concerning this message. Otherwise the testcase might associate a send or receive action with the wrong message. Please note that this ID is not passed to your system under test. The management of correlation IDs as well as the assignment to messages is done internally. Only the mapping between the request and response has to be done by the author of the test. As you can see in the following example, the value of the header MessageHeaders.ID is stored in the variable request#1 respectively request#2. This variable is reused in the receive action to identify the correct response from the server.

Java DSL
@CitrusTest
public void paralletTest() {

    parallel().actions(
        sequential().actions(
                http(b -> b.client(httpClient)
                        .send()
                        .post("/foo")
                        .extractFromHeader(MessageHeaders.ID, "request#1")
                        .payload("{ \"info\": \"foo\"}")),

                //SUT echoing the input

                http(b -> b.client(httpClient)
                        .receive()
                        .response(HttpStatus.OK)
                        .payload("{ \"info\": \"foo\"}")
                        .selector(
                            Collections.singletonMap(
                                MessageHeaders.ID,
                                "${request#1}")))
        ),
        sequential().actions(
                http(b -> b.client(httpClient)
                         .send()
                         .post("/boo")
                         .extractFromHeader(MessageHeaders.ID, "request#2")
                         .payload("{ \"info\": \"boo\"}")),

                //SUT echoing the input

                http(b -> b.client(httpClient)
                         .receive()
                         .response(HttpStatus.OK)
                         .payload("{ \"info\": \"boo\"}")
                         .selector(
                            Collections.singletonMap(
                                MessageHeaders.ID,
                                "${request#2}")))
        )
    );
}
Containers can easily wrap other containers. The example shows a simple combination of sequential and parallel containers that will achieve more complex execution logic. Actions inside the sequential container will execute one after another. But actions in parallel will be executed at the same time.

9.4. Iterate

Iterations are very powerful elements when describing complex logic. The container executes the embedded actions several times. The container will continue with looping as long as the defined breaking condition string evaluates to true . In case the condition evaluates to false the iteration will break an finish execution.

XML DSL
<testcase name="iterateTest">
    <actions>
        <iterate index="i" condition="i lt 5">
            <echo>
                <message>index is: ${i}</message>
            </echo>
        </iterate>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void iterateTest() {
    iterate().condition("i lt 5").index("i"))
        .actions(
            echo("index is: ${i}")
        );
}

The attribute "index" automatically defines a new variable that holds the actual loop index starting at "1". This index variable is available as a normal variable inside the iterate container. Therefore it is possible to print out the actual loop index in the echo action as shown in the above example.

The condition string is mandatory and describes the actual end of the loop. In iterate containers the loop will break in case the condition evaluates to false .

The condition string can be any Boolean expression and supports several operators:

lt

lower than

lt=

lower than equals

gt

greater than

gt=

greater than equals

=

equals

and

logical combining of two Boolean values

or

logical combining of two Boolean values

()

brackets

It is very important to notice that the condition is evaluated before the very first iteration takes place. The loop therefore can be executed 0-n times according to the condition value.

Now the boolean expression evaluation as described above is limited to very basic operation such as lower than, greater than and so on. We also can use Hamcrest matchers in conditions that are way more powerful than that.

XML DSL
<testcase name="iterateTest">
    <actions>
        <iterate index="i" condition="@assertThat(lessThan(5))@">
            <echo>
                <message>index is: ${i}</message>
            </echo>
        </iterate>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void iterateTest() {
    iterate().condition(lessThan(5)).index("i"))
        .actions(
            echo("index is: ${i}")
        );
}

In the example above we use Hamcrest matchers as condition. You can combine Hamcrest matchers and create very powerful condition evaluations here.

9.5. Repeat until true

Quite similar to the previously described iterate container this repeating container will execute its actions in a loop according to an ending condition. The condition describes a Boolean expression using the operators as described in the previous chapter.

The loop continues its work until the provided condition evaluates to true . It is very important to notice that the repeat loop will execute the actions before evaluating the condition. This means the actions get executed n-1 times.
XML DSL
<testcase name="iterateTest">
    <actions>
        <repeat-until-true index="i" condition="(i = 3) or (i = 5)">
            <echo>
                <message>index is: ${i}</message>
            </echo>
        </repeat-until-true>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void repeatTest() {
    repeat().until("(i gt 5) or (i = 3)").index("i"))
        .actions(
            echo("index is: ${i}")
        );
}

As you can see the repeat container is only executed when the iterating condition expression evaluates to false . By the time the condition is true execution is discontinued. You can use basic logical operators such as and, or and so on.

A more powerful way is given by Hamcrest matchers that are directly supported in condition expressions.

XML DSL
<testcase name="iterateTest">
    <actions>
        <repeat-until-true index="i" condition="@assertThat(anyOf(is(3), is(5))@">
            <echo>
                <message>index is: ${i}</message>
            </echo>
        </repeat-until-true>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void repeatTest() {
    repeat().until(anyOf(is(3), is(5)).index("i"))
        .actions(
            echo("index is: ${i}")
        );
}

The Hamcrest matcher usage simplifies the reading a lot. And it empowers you to combine more complex condition expressions. So I personally prefer this syntax.

9.6. Repeat on error until true

The next looping container is called repeat-on-error-until-true. This container repeats a group of actions in case one embedded action failed with error. In case of an error inside the container the loop will try to execute all embedded actions again in order to seek for overall success. The execution continues until all embedded actions were processed successfully or the ending condition evaluates to true and the error-loop will lead to final failure.

XML DSL
<testcase name="iterateTest">
    <actions>
        <repeat-onerror-until-true index="i" condition="i = 5">
            <echo>
                <message>index is: ${i}</message>
            </echo>
            <fail/>
        </repeat-onerror-until-true>
    </actions>
</testcase>
Java DSL designer
@CitrusTest
public void repeatOnErrorTest() {
    repeatOnError(
        echo("index is: ${i}"),
        fail("Force loop to fail!")
    ).until("i = 5").index("i");
}
Java DSL runner
@CitrusTest
public void repeatOnErrorTest() {
    repeatOnError().until("i = 5").index("i"))
        .actions(
            echo("index is: ${i}"),
            fail("Force loop to fail!")
        );
}

In the code example the error-loop continues four times as the <fail> action definitely fails the test. During the fifth iteration The condition "i=5" evaluates to true and the loop breaks its processing leading to a final failure as the test actions were not successful.

The overall success of the test case depends on the error situation inside the repeat-onerror-until-true container. In case the loop breaks because of failing actions and the loop will discontinue its work the whole test case is failing too. The error loop processing is successful in case all embedded actions were not raising any errors during an iteration.

The repeat-on-error container also offers an automatic sleep mechanism. This auto-sleep property will force the container to wait a given amount of time before executing the next iteration. We used this mechanism a lot when validating database entries. Let’s say we want to check the existence of an order entry in the database. Unfortunately the system under test is not very well performing and may need some time to store the new order. This amount of time is not predictable, especially when dealing with different hardware on our test environments (local testing vs. server testing). Following from that our test case may fail unpredictable only because of runtime conditions.

We can avoid unstable test cases that are based on these runtime conditions with the auto-sleep functionality.

XML DSL
<repeat-onerror-until-true auto-sleep="1000" condition="i = 5" index="i">
    <echo>
        <sql datasource="testDataSource">
            <statement>
              SELECT COUNT(1) AS CNT_ORDERS
              FROM ORDERS
              WHERE CUSTOMER_ID='${customerId}'
            </statement>
            <validate column="CNT_ORDERS" value="1"/>
        </sql>
    </echo>
</repeat-onerror-until-true>
Java DSL
@CitrusTest
public void repeatOnErrorTest() {
    repeatOnError().until("i = 5").index("i").autoSleep(1000))
        .actions(
            query(action -> action.dataSource(testDataSource)
                .statement("SELECT COUNT(1) AS CNT_ORDERS FROM ORDERS WHERE CUSTOMER_ID='${customerId}'")
                .validate("CNT_ORDERS", "1"))
        );
}

We surrounded the database check with a repeat-onerror container having the auto-sleep property set to 1000 milliseconds. The repeat container will try to check the database up to five times with an automatic sleep of 1 second before every iteration. This gives the system under test up to five seconds time to store the new entry to the database. The test case is very stable and just fits to the hardware environment. On slow test environments the test may need several iterations to successfully read the database entry. On very fast environments the test may succeed right on the first try.

We changed auto sleep time from seconds to milliseconds with Citrus 2.0 release. So if you are coming from previous Citrus versions be sure to now use proper millisecond values.

So fast environments are not slowed down by static sleep operations and slower environments are still able to execute this test case with high stability.

9.7. Timer

Timers are very useful containers when you wish to execute a collection of test actions several times at regular intervals. The timer component generates an event which in turn triggers the execution of the nested test actions associated with timer. This can be useful in a number of test scenarios for example when Citrus needs to simulate a heart beat or if you are debugging a test and you wist to query the contents of the database, to mention just a few. The following code sample should demonstrate the power and flexibility of timers:

XML DSL
<testcase name="timerTest">
      <actions>
        <timer id="forkedTimer" interval="100" fork="true">
          <echo>
            <message>I'm going to run in the background and let some other test actions run (nested action run ${forkedTimer-index} times)</message>
          </echo>
          <sleep milliseconds="50" />
        </timer>

        <timer repeatCount="3" interval="100" delay="50">
          <sleep milliseconds="50" />
          <echo>
            <message>I'm going to repeat this message 3 times before the next test actions are executed</message>
          </echo>
        </timer>

        <echo>
          <message>Test almost complete. Make sure all timers running in the background are stopped</message>
        </echo>
      </actions>
      <finally>
        <stop-timer timerId="forkedTimer" />
      </finally>
    </testcase>
Java DSL
@CitrusTest
public void timerTest() {

    timer()
        .timerId("forkedTimer")
        .interval(100L)
        .fork(true)
        .actions(
            echo("I'm going to run in the background and let some other test actions run (nested action run ${forkedTimer-index} times)"),
            sleep(50L)
        );

    timer()
        .repeatCount(3)
        .interval(100L)
        .delay(50L)
        .actions(
            sleep(50L),
            echo("I'm going to repeat this message 3 times before the next test actions are executed")
        );

    echo("Test almost complete. Make sure all timers running in the background are stopped");

    doFinally().actions(
        stopTimer("forkedTimer")
    );
}

In the above example the first timer (timerId = forkedTimer) is started in the background. By default timers are run in the current thread of execution but to start it in the background just use "fork=true". Every 100 milliseconds this timer emits an event which will result in the nested actions being executed. The nested 'echo' action outputs the number of times this timer has already been executed. It does this with the help of an 'index' variable, in this example ${forkedTimer-index}, which is named according to the timer id with the suffix '-index'. No limit is set on the number of times this timer should run so it will keep on running until either a nested test action fails or it is instructed to stop (more on this below).

The second timer is configured to run 3 times with a delay of 100 milliseconds between each iteration. Using the attribute 'delay' we can get the timer pause for 50 milliseconds before running the nested actions for the first time. The timer is configured to run in the current thread of execution so the last test action, the 'echo', has to wait for this timer to complete before it is executed.

So how do we tell the forked timer to stop running? If we forget to do this the timer will just execute indefinitely. To help us out here we can use the 'stop-timer' action. By adding this to the finally block we ensure that the timer will be stopped, even if some nested test action fails. We could have easily added it as a nested test action, to the forkedTimer for example, but if some other test action failed before the stop-timer was called, the timer would never stop.

You can also configure timers to run in the background using the 'parallel' container, rather than setting the attribute 'fork' to true. Using parallel allows more fine-grained control of the test and has the added advantage that all errors generated from a nester timer action are visible to the test executer. If an error occurs within the timer then the test status is set to failed. Using fork=true an error causes the timer to stop executing, but the test status is not influenced by this error.

9.8. Async

Now we deal with parallel execution of test actions. Nested actions inside an async container are executed in a separate thread. This allows to continue test execution without having to wait for actions inside the async container to complete. The test immediately continues to execute the next test actions, which will be executed in parallel to those actions inside the async container.

This mechanism comes in handy when a test action should be forked to the rest of the test. In send operations we were already able to achieve this by setting fork="true". With async containers, we’re able to execute all kinds of test actions asynchronous.

See some example to find out how it works.

XML DSL
<testcase name="asyncTest">
    <actions>
        <async>
            <actions>
                <send endpoint="fooEndpoint">
                    <message>...</message>
                </send>
                <receive endpoint="fooEndpoint">
                    <message>...</message>
                </echo>
            </actions>
        </async>

        <echo>
          <message>Continue with test</message>
        </echo>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void asyncTest() {
    async().actions(
            send(fooEndpoint)
                .message(fooRequest()),
            receive(fooEndpoint)
                .message(fooResponse())
        );

    echo("Continue with test");
}

The nested send and receive actions get executed in parallel to the other test actions in that test case. So the test will not wait for these actions to finish before executing next actions. Of course possible errors inside the async container will also cause the whole test case to fail. And the test will definitely wait for all async actions to be finished before finishing the whole test case. This safely lets us execute test actions in parallel to each other.

The async container also supports success and error callback actions.

XML DSL
<testcase name="asyncTest">
    <actions>
        <async>
            <actions>
                <send endpoint="fooEndpoint">
                    <message>...</message>
                </send>
                <receive endpoint="fooEndpoint">
                    <message>...</message>
                </echo>
                <success>
                    <echo><message>Success!</message></echo>
                </success>
                <error>
                    <echo><message>Failed!</message></echo>
                </error>
            </actions>
        </async>

        <echo>
          <message>Continue with test</message>
        </echo>
    </actions>
</testcase>

So you can add test actions which are executed based on the async test actions outcome success or error.

If you are using this container to send or receive messages, you have to use the unique correlation ID of the message to link the actions concerning this message. Otherwise the testcase might associate a send or receive action with the wrong message. Please note that this ID is not passed to your system under test. The management of correlation IDs as well as the assignment to messages is done internally. Only the mapping between the request and response has to be done by the author of the test. As you can see in the following example, the value of the header MessageHeaders.ID is stored in the variable request#1 respectively request#2. This variable is reused in the receive action to identify the correct response from the server.

Java DSL
@CitrusTest
public void testAsync() {

    async().actions(
        http(b -> b.client(httpClient)
                .send()
                .post("/foo")
                .extractFromHeader(MessageHeaders.ID, "request#1")
                .payload("{ \"info\": \"foo\"}")),

        //SUT echoing the input

        http(b -> b.client(httpClient)
                .receive()
                .response(HttpStatus.OK)
                .payload("{ \"info\": \"foo\"}")
                .selector(
                    Collections.singletonMap(
                        MessageHeaders.ID,
                        "${request#1}")))

    );

    async().actions(
        http(b -> b.client(httpClient)
                .send()
                .post("/boo")
                .extractFromHeader(MessageHeaders.ID, "request#2")
                .payload("{ \"info\": \"boo\"}")),

        //SUT echoing the input

        http(b -> b.client(httpClient)
                .receive()
                .response(HttpStatus.OK)
                .payload("{ \"info\": \"boo\"}")
                .selector(
                    Collections.singletonMap(
                        MessageHeaders.ID,
                        "${request#2}")))
    );
}

9.9. Wait

With this action you can make your test wait until a certain condition is satisfied. The attribute seconds defines the amount of time to wait in seconds. You can also use the milliseconds attribute for a more fine grained time value. The attribute interval defines the amount of time to wait between each check. The interval is always specified as millisecond time interval.

If the check does not exceed within the defined overall waiting time then the test execution fails with an appropriate error message. There are different types of conditions to check.

http

This condition is based on a Http request call on a server endpoint. Citrus will wait until the Http response is as defined (e.g. Http 200 OK). This is useful when you want to wait for a server to start.

file

This condition checks for the existence of a file on the local file system. Citrus will wait until the file is present.

message

This condition checks for the existence of a message in the local message store of the current test case. Citrus will wait until the message with the given name is present.

action

This condition executes another test action and checks for successful execution. Citrus will wait until the nested action is executed without any errors.

When should somebody use this action? This action is very useful when you want your test to wait for a certain event to occur before continuing with the test execution. For example if you wish that your test waits until a Docker container is started or for an application to create a log file before continuing, then use this action. You can also create your own condition statements and bind it to the test action.

9.9.1. Http condition

Next let us have a look at a simple example:

XML DSL
<testcase name="waitTest">
    <actions>
        <wait seconds="10" interval="2000" >
          <http url="http://sample.org/resource" statusCode="200" timeout="2000" />
        <wait/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void waitTest() {
    waitFor().http().seconds(10L).interval(2000L).url("http://sample.org/resource");
}

The example waits for some Http server resource to be available with Http 200 OK response. Citrus will use HEAD request method by default. You can set the request method with the method attribute on the Http condition.

9.9.2. File condition

Next let us have a look at the file condition usage:

XML DSL
<testcase name="waitTest">
    <actions>
        <wait seconds="10" interval="2000" >
          <file path="path/to/resource/file.txt" />
        <wait/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void waitTest() {
    waitFor().file().path("path/to/resource/file.txt");
}

Citrus checks for the file to exist under the given path. Only if the file exists the test will continue with further test actions.

9.9.3. Message condition

Next let us have a look at the message condition usage:

XML DSL
<testcase name="waitTest">
    <actions>
        <wait seconds="10" interval="2000" >
          <message name="helloRequest" />
        <wait/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void waitTest() {
    waitFor().message().name("helloRequest");
}

Citrus checks for the message with the name helloRequest in the local message store. Only if the message with the given name is found the test will continue with further test actions. The local message store is automatically filled with all exchanged messages (send or receive) in a test case. The message names are defined in the respective send or receive operations in the test.

9.9.4. Action condition

Now we would like to wait for some other test action to execute.

XML DSL
<testcase name="waitTest">
    <actions>
        <wait seconds="10" interval="2000" >
          <action>
            <receive endpoint="jmsEndpoint">
                <message>
                    <payload>Wait for me!</payload>
                </message>
            </receive>
          </action>
        <wait/>
    </actions>
</testcase>
Java DSL
@CitrusTest
public void waitTest() {
    waitFor()
        .execution()
        .action(receive(jmsEndpoint).payload("Wait for me!"));
}

You can add any test action to the wait condition so you can execute any other action and wait for its success. This enables us to also use the full send and receive operations on other message transports.

9.10. Custom containers

In case you have a custom action container implementation you might also want to use it in Java DSL. The action containers are handled with special care in the Java DSL because they have nested actions. So when you call a test action container in the Java DSL you always have something like this:

Java DSL
@CitrusTest
public void containerTest() {
    echo("This echo is outside of the action container");

    sequential()
        .actions(
            echo("Inside"),
            echo("Inside once more"),
            echo("And again: Inside!")
        );

    echo("This echo is outside of the action container");
}

Now the three nested actions are added to the action sequential container rather than to the test case itself although we are using the same action Java DSL methods as outside the container. This mechanism is only working because Citrus is handling test action containers with special care.

A custom test action container implementation could look like this:

public class ReverseActionContainer extends AbstractActionContainer {
    @Override
    public void doExecute(TestContext context) {
        for (int i = getActions().size(); i > 0; i--) {
            getActions().get(i-1).execute(context);
        }
    }
}

The container logic is very simple: The container executes the nested actions in reverse order. As already mentioned Citrus needs to take special care on all action containers when executing a Java DSL test. This is why you should not execute a custom test container implementation on your own.

@CitrusTest
public void containerTest() {
    ReverseActionContainer reverseContainer = new ReverseActionContainer();
    reverseContainer.addTestAction(new EchoAction().setMessage("Foo"));
    reverseContainer.addTestAction(new EchoAction().setMessage("Bar"));
    run(reverseContainer);
}

The above custom container execution is going to fail with internal error as the Citrus Java DSL was not able to recognise the action container as it should be. Also the EchoAction instance creation is not very comfortable. Instead you can use a special container Java DSL syntax also with your custom container implementation:

@CitrusTest
public void containerTest() {
    container(new ReverseActionContainer()).actions(
        echo("Foo"),
        echo("Bar")
    );
}

The custom container implementation now works fine with the automatically nested echo actions. And we are able to use the usual Java DSL syntactic sugar for test actions like echo .

In a next step we add a custom superclass for all our test classes which provides a helper method for the custom container implementation in order to have a even more comfortable syntax.

Java DSL
public class CustomCitrusBaseTest extends TestNGCitrusTestDesigner {

    public AbstractTestContainerBuilder<ReverseActionContainer> reverse() {
        return container(new ReverseActionContainer());
    }
}

Now all subclasses can use the new reverse method for calling the custom container implementation.

@CitrusTest
public void containerTest() {
    reverse().actions(
        echo("Foo"),
        echo("Bar")
    );
}

Nice! This is how we should integrate customized test action containers to the Citrus Java DSL.

10. Endpoints

In one of the previous chapters we have discussed the basic test case structure as we introduced variables and test actions. The <actions> section contains a list of test actions that take place during the test case. Each test action is executed in sequential order by default. Citrus offers several built-in test actions that the user can choose from to construct a complex testing workflow without having to code everything from scratch. In particular Citrus aims to provide all the test actions that you need as predefined components ready for you to use. The goal is to minimize the coding effort for you so you can concentrate on the test logic itself.

Exactly the same approach is used in Citrus to provide ready-to-use endpoint component for connecting to different message transports. There are several ways in an enterprise application to exchange messages with some other application. We have synchronous interfaces like Http and SOAP WebServices. We have asynchronous messaging with JMS or file transfer FTP interfaces.

Citrus provides endpoint components as client and server to connect with these typical message transports. So you as a tester must not care about how to send a message to a JMS queue. The Citrus endpoints are configured in the Spring application context and receive endpoint specific properties like endpoint uri or ports or message timeouts as configuration.

The next figure shows a typical message sending endpoint component in Citrus:

figure_002.jpg

The endpoint producer publishes messages to a destination. This destination can be a JMS queue/topic, a SOAP WebService endpoint, a Http URL, a FTP folder destination and so on. The producer just takes a previously defined message definition (header and body) and sends it to the message destination.

Similar to that Citrus defines the several endpoint consumer components to consume messages from destinations. This can be a simple subscription on message channels and JMS queues/topics. In case of SOAP WebServices and Http GET/POST things are more complicated as we have to provide a server component that clients can connect to. We will handle server related communication in more detail later on. For now the endpoint consumer component in its most simple way is defined like this:

figure_003.jpg

This is all you need to know about Citrus endpoints. We have mentioned that the endpoints are defined in the Spring application context. Let’s have a simple example that shows the basic idea:

<citrus-jms:endpoint id="helloServiceEndpoint"
        destination-name="Citrus.HelloService.Request.Queue"
        connection-factory="myConnectionFactory"/>

This is a simple JMS endpoint component in Citrus. The endpoint XML bean definition follows a custom XML namespace and defines endpoint specific properties like the JMS destination name and the JMS connection factory. The endpoint id is a significant property as the test cases will reference this endpoint when sending and receiving messages by its identifier.

In the next sections you will learn how a test case uses those endpoint components for producing and consuming messages.

10.1. Send messages

The <send> action in a test case publishes messages to a destination. The actual message transport connection is defined with the endpoint component. The test case simply defines the message contents and references a predefined message endpoint component by its identifier. Endpoint specific configurations are centralized in the Spring bean application context while multiple test cases can reference the endpoint to actually publish the constructed message to a destination. There are several message endpoint implementations in Citrus available representing different transport protocols like JMS, SOAP, HTTP, TCP/IP and many more.

Again the type of transport to use is not specified inside the test case but in the message endpoint definition. The separation of concerns (test case/message sender transport) gives us a good flexibility of our test cases. The test case does not know anything about connection factories, queue names or endpoint uri, connection timeouts and so on. The transport internals underneath a sending test action can change easily without affecting the test case definition. We will see later in this document how to create different message endpoints for various transports in Citrus. For now we concentrate on constructing the message content to be sent.

We assume that the message’s body will be plain XML format. Citrus uses XML as the default data format for message body data. But Citrus is not limited to XML message format though; you can always define other message data formats such as JSON, plain text, CSV. As XML is still a very popular message format in enterprise applications and message-based solution architectures we have this as a default format. Anyway Citrus works best on XML bodies and you will see a lot of example code in this document using XML. Finally let us have a look at a first example how a sending action is defined in the test.

XML DSL
<testcase name="SendMessageTest">
    <description>Basic send message example</description>

    <actions>
        <send endpoint="helloServiceEndpoint">
            <message>
                <payload>
                    <TestMessage>
                        <Text>Hello!</Text>
                    </TestMessage>
                </payload>
            </message>
            <header>
                <element name="Operation" value="sayHello"/>
            </header>
        </send>
    </actions>
</testcase>

Now lets have a closer look at the sending action. The 'endpoint' attribute might catch your attention first. This attribute references the message endpoint in Citrus configuration by its identifier. As previously mentioned the message endpoint definition lives in a separate configuration file and contains the actual message transport settings. In this example the "helloServiceEndpoint" is referenced which is a JMS endpoint for sending out messages to a JMS queue for instance.

The test case is not aware of any transport details, because it does not have to. The advantages are obvious: On the one hand multiple test cases can reference the message endpoint definition for better reuse. Secondly test cases are independent of message transport details. So connection factories, user credentials, endpoint uri values and so on are not present in the test case.

In other words the "endpoint" attribute of the <send> element specifies which message endpoint definition to use and therefore where the message should go to. Once again all available message endpoints are configured in a separate Citrus configuration file. Be sure to always pick the right message endpoint type in order to publish your message to the right destination.

If you do not like the XML language you can also use pure Java code to define the same test. In Java you would also make use of the message endpoint definition and reference this instance. The same test as shown above in Java DSL looks like this:

Java DSL designer
import org.testng.ITestContext;
import org.testng.annotations.Test;
import com.consol.citrus.annotations.CitrusTest;
import com.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;

@Test
public class SendMessageTestDesigner extends TestNGCitrusTestDesigner {

    @CitrusTest(name = "SendMessageTest")
    public void sendMessageTest() {
        description("Basic send message example");

        send("helloServiceEndpoint")
            .message()
            .body("<TestMessage>" +
                        "<Text>Hello!</Text>" +
                    "</TestMessage>")
            .header("Operation", "sayHello");
    }
}

Instead of using the XML tags for send we use methods from TestNGCitrusTestDesigner class. The same message endpoint is referenced within the send message action. The body is constructed as plain Java character sequence which is a bit verbose. We will see later on how we can improve this. For now it is important to understand the combination of send test action and a message endpoint.

It is good practice to follow naming conventions when defining names for message endpoints. The intended purpose of the message endpoint as well as the sending/receiving actor should be clear when choosing the name. For instance messageEndpoint1, messageEndpoint2 will not give you much hints to the purpose of the message endpoint.

This is basically how to send messages in Citrus. The test case is responsible for constructing the message content while the predefined message endpoint holds transport specific settings. Test cases reference endpoint components to publish messages to the outside world. This is just the start of action. Citrus supports a whole package of other ways how to define and manipulate the message contents. Read more about message sending actions in actions-send.

10.2. Receive messages

Now we have a look at the message receiving part inside the test. A simple example shows how it works.

XML DSL
<receive endpoint="helloServiceEndpoint">
    <message>
        <payload>
            <TestMessage>
                <Text>Hello!</Text>
            </TestMessage>
        </payload>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
    </header>
</receive>

If we recap the send action of the previous chapter we can identify some common mechanisms that apply for both sending and receiving actions. The test action also uses the endpoint attribute for referencing a predefined message endpoint. This time we want to receive a message from the endpoint. Again the test is not aware of the transport details such as JMS connections, endpoint uri, and so on. The message endpoint component encapsulates this information.

Before we go into detail on validating the received message we have a quick look at the Java DSL variation for the receive action. The same receive action as above looks like this in Java DSL.

Java DSL designer
@CitrusTest
public void messagingTest() {
    receive("helloServiceEndpoint")
        .message()
        .body("<TestMessage>" +
                    "<Text>Hello!</Text>" +
                "</TestMessage>")
        .header("Operation", "sayHello");
}

The receive action waits for a message to arrive. The whole test execution is stopped while waiting for the message. This is important to ensure the step by step test workflow processing. Of course you can specify message timeouts so the receiver will only wait a given amount of time before raising a timeout error. Following from that timeout exception the test case fails as the message did not arrive in time. Citrus defines default timeout settings for all message receiving tasks.

At this point you know the two most important test actions in Citrus. Sending and receiving actions will become the main components of your integration tests when dealing with loosely coupled message based components in a enterprise application environment. It is very easy to create complex message flows, meaning a sequence of sending and receiving actions in your test case. You can replicate use cases and test your message exchange with extended message validation capabilities. See actions-receive for a more detailed description on how to validate incoming messages and how to expect message contents in a test case.

10.3. Local message store

All messages that are sent and received during a test case are stored in a local memory storage. This is because we might want to access the message content later on in a test case. We can do so by using message store functions for loading messages that have been exchanged earlier in the test. When storing a message in the local storage Citrus uses a message name as identifier key. This message name is later on used to access the message. You can define the message name in any send or receive action:

XML DSL
<receive endpoint="helloServiceEndpoint">
    <message name="helloMessage">
        <payload>
            <TestMessage>
                <Text>Hello!</Text>
            </TestMessage>
        </payload>
    </message>
    <header>
        <element name="Operation" value="sayHello"/>
    </header>
</receive>
Java DSL designer
@CitrusTest
public void messagingTest() {
    receive("helloServiceEndpoint")
        .message()
        .name("helloMessage")
        .body("<TestMessage>" +
                    "<Text>Hello!</Text>" +
                "</TestMessage>")
        .header("Operation", "sayHello");
}

The receive operation above set the message name to helloMessage. The message received is automatically stored in the local storage with that name. You can access the message content for instance by using a function:

<echo>
    <message>citrus:message(helloMessage.body())</message>
</echo>

The function loads the helloMessage and prints the body information with the echo test action. In combination with Xpath or JsonPath functions this mechanism is a good way to access the exchanged message contents later in a test case.

The storage is for both sent and received messages in a test case. The storage is per test case and contains all sent and received messages.

When no explicit message name is given the local storage will construct a default message name. The default name is built from the action (send or receive) plus the endpoint used to exchange the message. For instance:

send(helloEndpoint)
receive(helloEndpoint)

The names above would be generated by a send and receive operation on the endpoint named helloEndpoint.

The message store is not able to handle multiple message of the same name in one test case. So messages with identical names will overwrite existing messages in the local storage.

Now we have seen the basic endpoint concept in Citrus. The endpoint components represent the connections to the test boundary systems. This is how we can connect to the system under test for message exchange. And this is our main goal with this integration test framework. We want to provide easy access to common message transports on client and server side so that we can test the communication interfaces on a real message transport exchange.

11. Direct endpoint

Direct endpoints represent the default in memory messaging solution in Citrus. Producer and consumer components are linked via queues exchanging messages in memory.

The direct endpoint configuration components use the default "citrus" configuration namespace and schema definition. Include this namespace into your Spring bean configuration in order to use the Citrus configuration elements. The namespace URI and schema location are added to the Spring configuration XML file as follows.
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:citrus="http://www.citrusframework.org/schema/config"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.citrusframework.org/schema/config
        http://www.citrusframework.org/schema/config/citrus-config.xsd">

    [...]

</beans>

Right now you are able to use the Citrus XML elements in order to define the direct endpoint components.

11.1. Channel endpoint

Citrus offers a direct endpoint component that is able to create producers and consumers. Producer and consumer send and receive messages both to and from a direct endpoint. By default, the endpoint is asynchronous when configured in the Citrus context.

Java
@Bean
public ChannelEndpoint helloEndpoint() {
    return new ChannelEndpointBuilder()
        .queue("helloChannel")
        .build();
}

@Bean
public MessageSelectingQueueChannel helloChannel() {
    return new MessageSelectingQueueChannel();
}
XML
<citrus:direct-endpoint id="helloEndpoint" queue="helloChannel"/>

<citrus:queue id="helloChannel"/>

The Citrus direct endpoint references a queue directly. Inside your test case you can reference the Citrus endpoint as usual to send and receive messages.

The message sender is now ready to publish messages on the defined queue. The communication is supposed to be asynchronous, so the producer is not able to process any reply message. We will deal with synchronous communication and reply messages later in this chapter. You can reference the id of the endpoint in a send and receive test action.

Java
when(send("helloEndpoint")
        .message()
        .body("<v1:HelloRequest xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello World!</v1:Text>" +
            "</v1:HelloRequest>"));

then(receive("helloEndpoint")
        .message()
        .body("<v1:HelloResponse xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello Citrus!</v1:Text>" +
            "</v1:HelloResponse>"));
XML
<send endpoint="helloEndpoint">
    <message>
        <payload>
            <v1:HelloRequest xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello World!</v1:Text>
            </v1:HelloRequest>
        </payload>
    </message>
</send>

<receive endpoint="helloEndpoint">
    <message>
        <payload>
            <v1:HelloResponse xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello Citrus!</v1:Text>
            </v1:HelloResponse>
        </payload>
    </message>
</receive>

You can send and receive messages from the same direct endpoint. As usual the receiver connects to the message destination and waits for messages to arrive. The user can set a receive timeout which is set to 5000 milliseconds by default. In case no message was received in this time frame the receiver raises timeout errors and the test fails.

11.2. Synchronous direct endpoints

The synchronous direct producer publishes messages and waits synchronously for the response to arrive on a reply queue destination. The reply queue name is set in the message headers. The counterpart in this communication must send its reply to that queue. The basic configuration for a synchronous direct endpoint component looks like follows:

Java
@Bean
public ChannelSyncEndpoint helloEndpoint() {
    return new ChannelSyncEndpointBuilder()
        .queue("helloChannel")
        .replyTimeout(1000L)
        .pollingInterval(1000L)
        .build();
}
XML
<citrus:direct-sync-endpoint id="helloSyncEndpoint"
                            queue="helloChannel"
                            reply-timeout="1000"
                            polling-interval="1000"/>

Synchronous direct endpoints usually do poll for synchronous reply messages for processing the reply messages. The poll interval is an optional setting in order to manage the amount of reply message handshake attempts. When the endpoint was able to receive the reply message synchronously the test case can verify the reply.

In case all polling attempts have failed the action raises a timeout error, and the test will fail.

By default, the direct endpoint uses temporary reply queue destinations. The temporary reply queues are only used once for a single communication handshake. The temporary reply queue is deleted automatically.

When sending a message to this endpoint in the first place the producer will wait synchronously for the response message to arrive on the reply queue. You can receive the reply message in your test case using the same endpoint component. So we have two actions on the same endpoint, first send then receive.

Java
when(send("helloSyncEndpoint")
        .message()
        .body("<v1:HelloRequest xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello World!</v1:Text>" +
            "</v1:HelloRequest>"));

then(receive("helloSyncEndpoint")
        .message()
        .body("<v1:HelloResponse xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello Citrus!</v1:Text>" +
            "</v1:HelloResponse>"));
XML
<send endpoint="helloSyncEndpoint">
    <message>
        <payload>
            <v1:HelloRequest xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello World!</v1:Text>
            </v1:HelloRequest>
        </payload>
    </message>
</send>

<receive endpoint="helloSyncEndpoint">
    <message>
        <payload>
            <v1:HelloResponse xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello Citrus!</v1:Text>
            </v1:HelloResponse>
        </payload>
    </message>
</receive>

This is how you handle synchronous communication as a sender. You publish messages to a queue and wait for reply messages on a temporary reply queue. The next section deals with the same synchronous communication, but now Citrus will receive a request and send a synchronous reply message to a temporary reply queue.

As usual the reply queue name is stored in the message headers. Citrus handles this synchronous communication with the same synchronous direct endpoint component. The handling of temporary reply destinations is done automatically behind the scenes.

So we have again two actions in our test case, but this time first receive then send.

Java
when(receive("helloSyncEndpoint")
        .message()
        .body("<v1:HelloRequest xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello World!</v1:Text>" +
            "</v1:HelloRequest>"));

then(send("helloSyncEndpoint")
        .message()
        .body("<v1:HelloResponse xmlns:v1=\"http://citrusframework.org/schemas/HelloService.xsd\">" +
                "<v1:Text>Hello Citrus!</v1:Text>" +
            "</v1:HelloResponse>"));
XML
<receive endpoint="helloSyncEndpoint">
    <message>
        <payload>
            <v1:HelloRequest xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello World!</v1:Text>
            </v1:HelloRequest>
        </payload>
    </message>
</receive>

<send endpoint="helloSyncEndpoint">
    <message>
        <payload>
            <v1:HelloResponse xmlns:v1="http://citrusframework.org/schemas/HelloService.xsd">
                <v1:Text>Hello Citrus!</v1:Text>
            </v1:HelloResponse>
        </payload>
    </message>
</send>

11.3. Message selectors

A queue can hold multiple messages at the same time. Usually you receive messages using first-in-first-out pattern. Message selectors enable you to select messages form that queue so you can pick messages form a queue based on a selector evaluation.

Citrus introduces a special queue message queue implementation that support message selectors.

Java
@Bean
public MessageSelectingQueueChannel helloChannel() {
    return new MessageSelectingQueueChannel();
}
XML
<citrus:queue id="orderChannel" capacity="5"/>

We can add a capacity attribute for this queue. A receive test action makes use of message selectors on header values as described in message-selector.

In addition to that we have implemented other message filter possibilities on message queues that we discuss in the next sections.

11.4. Payload matching selector

You can select messages based on the payload content. Either you define the expected payload as an exact match in the selector or you make use of Citrus validation matchers which is more adequate in most scenarios.

Assume there are two different plain text messages living on a message queue waiting to be picked up by a consumer.

Hello, welcome!
GoodBye, see you next time!

The tester would like to pick up the message starting with GoodBye in our test case. The other messages should be left on the queue as we are not interested in it right now. We can define a payload matching selector in the receive action like this:

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("payload", "@startsWith(GoodBye)@"))
        .message()
        .body("GoodBye, see you next time!"));
XML
<receive endpoint="orderChannelEndpoint">
    <selector>
        <element name="payload" value="@startsWith(GoodBye)@"/>
    </selector>
    <message>
        <payload>GoodBye, see you next time!</payload>
    </message>
</receive>

The Citrus receiver picks up the GoodBye from the queue selected via the payload matching expression defined in the selector element. Of course, you can also combine message header selectors and payload matching selectors as shown in this example below where a message header sequenceId is added to the selection logic.

Java
Map<String, String> selectorMap = new HashMap<>();
selectorMap.put("payload", "@startsWith(GoodBye)@");
selectorMap.put("sequenceId", "1234");

when(receive("orderChannelEndpoint")
        .selector(selector)
        .message()
        .body("GoodBye, see you next time!"));
XML
<selector>
    <element name="payload" value="@startsWith(GoodBye)@"/>
    <element name="sequenceId" value="1234"/>
</selector>

11.5. Root QName selector

As a special payload matching selector you can use the XML root QName of your message as selection criteria when dealing with XML message content. Let’s see how this works in a small example:

We have two different XML messages on a message queue waiting to be picked up by a consumer.

<HelloMessage xmlns="http://citrusframework.org/schema">Hello Citrus</HelloMessage>
<GoodbyeMessage xmlns="http://citrusframework.org/schema">Goodbye Citrus</GoodbyeMessage>

We would like to pick up the GoodbyeMessage in our test case. The HelloMessage should be left on the message queue as we are not interested in it right now. We can define a root qname message selector in the receive action like this:

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("root-qname", "GoodbyeMessage"))
        .message()
        .body("<GoodbyeMessage xmlns=\"http://citrusframework.org/schema\">Goodbye Citrus</GoodbyeMessage>"));
XML
<receive endpoint="orderChannelEndpoint">
    <selector>
        <element name="root-qname" value="GoodbyeMessage"/>
    </selector>
    <message>
        <payload>
            <GoodbyeMessage xmlns="http://citrusframework.org/schema">Goodbye Citrus</GoodbyeMessage>
        </payload>
    </message>
</receive>

The Citrus receiver picks up the GoodbyeMessage from the queue selected via the root qname of the XML message payload. Of course, you can also combine message header selectors and root qname selectors as shown in this example below where a message header sequenceId is added to the selection logic.

Java
Map<String, String> selectorMap = new HashMap<>();
selectorMap.put("root-qname", "GoodbyeMessage");
selectorMap.put("sequenceId", "1234");

when(receive("orderChannelEndpoint")
        .selector(selector)
        .message()
        .body("GoodBye, see you next time!"));
XML
<selector>
    <element name="root-qname" value="GoodbyeMessage"/>
    <element name="sequenceId" value="1234"/>
</selector>

As we deal with XML qname values, we can also use namespaces in our selector root qname selection.

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("root-qname", "{http://citrusframework.org/schema}GoodbyeMessage"))
        .message()
        .body("<GoodbyeMessage xmlns=\"http://citrusframework.org/schema\">Goodbye Citrus</GoodbyeMessage>"));
XML
<selector>
    <element name="root-qname" value="{http://citrusframework.org/schema}GoodbyeMessage"/>
</selector>

11.6. Xpath selector

It is also possible to evaluate some XPath expression on the message payload in order to select a message from a message queue. The XPath expression outcome must match an expected value and only then the message is consumed form the queue.

The syntax for the XPath expression is to be defined as the element name like this:

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("xpath://Order/status", "pending"))
        .message()
        .body("<Order><status>pending</status></Order>"));
XML
<selector>
    <element name="xpath://Order/status" value="pending"/>
</selector>

The message selector looks for order messages with status="pending" in the message payload. This means that following messages would get accepted/declined by the message selector.

<Order><status>pending</status></Order> <!-- ACCEPTED -->
<Order><status>finished</status></Order> <!-- NOT ACCEPTED -->

Of course, you can also use XML namespaces in your XPath expressions when selecting messages from queues.

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("xpath://ns1:Order/ns1:status", "pending"))
        .message()
        .body("<Order><status>pending</status></Order>"));
XML
<selector>
    <element name="xpath://ns1:Order/ns1:status" value="pending"/>
</selector>

Namespace prefixes must match the incoming message - otherwise the XPath expression will not work as expected. In our example the message should look like this:

<ns1:Order xmlns:ns1="http://citrus.org/schema"><ns1:status>pending</ns1:status></ns1:Order>

Knowing the correct XML namespace prefix is not always easy. If you are not sure which namespace prefix to choose Citrus ships with a dynamic namespace replacement for XPath expressions. The XPath expression looks like this and is most flexible:

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap(
                "xpath://{http://citrus.org/schema}:Order/{http://citrus.org/schema}:status", "pending"))
        .message()
        .body("<Order><status>pending</status></Order>"));
XML
<selector>
    <element name="xpath://{http://citrus.org/schema}:Order/{http://citrus.org/schema}:status"
                value="pending"/>
</selector>

This will match all incoming messages regardless the XML namespace prefix that is used.

11.7. JsonPath selector

It is also possible to evaluate some JsonPath expression on the message payload in order to select a message from a message queue. The JsonPath expression outcome must match an expected value and only then the message is consumed form the queue.

The syntax for the JsonPath expression is to be defined as the element name like this:

Java
when(receive("orderChannelEndpoint")
        .selector(Collections.singletonMap("jsonPath:$.order.status", "pending"))
        .message()
        .body("{ \"order\": { \"status\": \"pending\" } }"));
XML
<selector>
    <element name="jsonPath:$.order.status" value="pending"/>
</selector>

The message selector looks for order messages with status="pending" in the message payload. This means that following messages would get accepted/declined by the message selector.

{ "order": { "status": "pending" } } //ACCEPTED
{ "order": { "status": "finished" } } //NOT ACCEPTED

12. JMS support

Citrus provides support for sending and receiving JMS messages. We have to separate between synchronous and asynchronous communication. So in this chapter we explain how to work with JMS message endpoints for synchronous and asynchronous communication

The JMS components in Citrus are kept in a separate Maven module. If not already done so you have to include the module as Maven dependency to your project
Maven module dependency
<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-jms</artifactId>
  <version>${citrus.version}</version>
</dependency>

Citrus provides a "citrus-jms" configuration namespace and schema definition for JMS related components and features. Include this namespace into your Spring configuration in order to use the Citrus JMS configuration elements. The namespace URI and schema location are added to the Spring configuration XML file as follows.

Spring configuration namespace
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:citrus-jms="http://www.citrusframework.org/schema/jms/config"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.citrusframework.org/schema/jms/config
       http://www.citrusframework.org/schema/jms/config/citrus-jms-config.xsd">

    [...]

</beans>

Now you are able to use customized Citrus XML elements in order to define the JMS endpoint components.

12.1. JMS endpoints

By default, Citrus JMS endpoints are asynchronous. Asynchronous messaging means that the endpoint will not wait for a response message after sending a message.

The test case itself should not know about JMS transport details like queue names or connection credentials. This information is stored in the endpoint component configuration that lives in the basic project configuration files in Citrus. So let us have a look at a simple JMS message endpoint configuration in Citrus.

Java
@Bean
public JmsEndpoint helloServiceEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Request.Queue")
        .timeout(10000L)
        .build();
}
XML
<citrus-jms:endpoint id="helloServiceEndpoint"
          destination-name="Citrus.HelloService.Request.Queue"
          timeout="10000"/>

The endpoint component receives a unique id as well as a JMS destination name. This can be a queue or topic destination. JMS topics are described later on in this chapter. For now the timeout setting completes the first JMS endpoint component definition example.

In addition to the destination-name attribute you can also provide a reference to a destination implementation.
Java
@Bean
public JmsEndpoint helloServiceEndpoint() {
    return new JmsEndpointBuilder()
        .destination(helloServiceQueue())
        .build();
}

@Bean
public ActiveMQQueue helloServiceQueue() {
    return new ActiveMQQueue("Citrus.HelloService.Request.Queue");
}
XML
<citrus-jms:endpoint id="helloServiceEndpoint"
                     destination="helloServiceQueue"/>

<amq:queue id="helloServiceQueue" physicalName="Citrus.HelloService.Request.Queue"/>

The destination attribute references to a JMS destination object in the same Spring application context. In the example above we used the ActiveMQ queue destination component. The destination reference can also refer to a JNDI lookup for instance.

The endpoint needs a JMS connection factory for connecting to a JMS message broker. The connection factory is also added as component bean to the Citrus project (e.g. in the Spring application context).

Java
@Bean
public ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
    factory.setBrokerURL("tcp://localhost:61616");
    return factory;
}
XML
<bean id="connectionFactory"
         class="org.apache.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616" />
</bean>

The JMS connection factory receives the broker URL and is able to hold many other connection specific options. In this example we use the Apache ActiveMQ connection factory implementation as we want to use the ActiveMQ message broker. Citrus works with a bean id connectionFactory. All Citrus JMS component will automatically recognize this connection factory.

The configuration makes it very easy to connect to other JMS broker implementations, too (e.g. Apache ActiveMQ, TIBCO Enterprise Messaging Service, IBM Websphere MQ). Just add the required connection factory implementation as connectionFactory bean.
All JMS endpoint components in Citrus will automatically load the factory named connectionFactory. You can use the connection-factory endpoint attribute in order to use another connection factory instance with different bean names.
Java
@Bean
public JmsEndpoint helloServiceEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Request.Queue")
        .connectionFactory("myConnectionFactory")
        .build();
}
XML
<citrus-jms:endpoint id="helloServiceEndpoint"
      destination-name="Citrus.HelloService.Request.Queue"
      connection-factory="myConnectionFactory"/>

As an alternative to that you may want to use a special Spring jms template implementation as custom bean in your endpoint.

Java
@Bean
public JmsEndpoint helloServiceEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Request.Queue")
        .jmsTemplate("myJmsTemplate")
        .build();
}
XML
<citrus-jms:endpoint id="helloServiceEndpoint"
              destination-name="Citrus.HelloService.Request.Queue"
              jms-template="myJmsTemplate"/>

The endpoint is now ready to be used inside a test case. You can send or receive messages using this endpoint. The test actions reference the JMS endpoint using its unique identifier. When sending a message the message endpoint creates a JMS message producer and will simply publish the message to the defined JMS destination. As the communication is asynchronous by default the producer does not wait for a synchronous response.

When receiving messages the endpoint creates a JMS consumer on the JMS destination. The endpoint then acts as a message driven listener. This means that the message consumer connects to the given destination and waits for messages to arrive.

Java
when(send("helloServiceEndpoint")
        .message()
        .body("..."));

then(receive("helloServiceEndpoint")
        .message()
        .body("..."));
XML
<testcase name="jmsMessagingTest">
    <actions>
        <send endpoint="helloServiceEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </send>

        <receive endpoint="helloServiceEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </receive>
    </actions>
</testcase>

12.2. JMS synchronous endpoints

When using synchronous message endpoints Citrus will manage a reply destination for receiving a synchronous response message on the reply destination. The following figure illustrates that we now have two destinations in our communication scenario.

figure_006.jpg

The synchronous message endpoint component is similar to the asynchronous variant that has been discussed before. The only difference is that the endpoint will automatically manage a reply destination behind the scenes. By default, Citrus uses temporary reply destinations that get automatically deleted after the communication handshake is done. Again we need to use a JMS connection factory in the configuration as the component needs to connect to a JMS message broker.

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.InOut.Queue")
        .build();
}
XML
<citrus-jms:sync-endpoint id="helloServiceSyncEndpoint"
          destination-name="Citrus.HelloService.InOut.Queue"
          timeout="10000"/>

The synchronous component defines a target destination which again is either a queue or topic destination. The endpoint will create the temporary reply destinations on its own. As soon as the endpoint has published a request message it waits synchronously for the response message to arrive at the reply destination. You can receive this reply message in your test case by referencing this same endpoint in a receiving test action. The timeout setting defines how long the endpoint waits for the synchronous reply. In case no reply message arrives in time a message timeout error is raised respectively.

See the following example test case which references the synchronous message endpoint in its send and receive test action in order to send out a message and wait for the synchronous response.

Java
when(send("helloServiceSyncEndpoint")
        .message()
        .body("..."));

then(receive("helloServiceSyncEndpoint")
        .message()
        .body("..."));
XML
<testcase name="jmsSyncMessagingTest">
    <actions>
        <send endpoint="helloServiceSyncEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </send>

        <receive endpoint="helloServiceSyncEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </receive>
    </actions>
</testcase>

We initiated the synchronous communication by sending a message on the synchronous endpoint. The second step then receives the synchronous message on the temporary reply destination that was automatically created for you.

If you rather want to define a static reply destination you can do so, too. The static reply destination is not deleted after the communication handshake. You may need to work with message selectors then in order to pick the right response message that belongs to a specific communication handshake. You can define a static reply destination on the synchronous endpoint component as follows.

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.InOut.Queue")
        .replyDestination("Citrus.HelloService.Reply.Queue")
        .build();
}
XML
<citrus-jms:sync-endpoint id="helloServiceSyncEndpoint"
          destination-name="Citrus.HelloService.InOut.Queue"
          reply-destination-name="Citrus.HelloService.Reply.Queue"
          timeout="10000"/>

Instead of using the reply-destination-name feel free to use the destination reference with reply-destination attribute. Again you can use a JNDI lookup then to reference a destination object.

Be aware of permissions that are mandatory for creating temporary destinations. Citrus tries to create temporary queues on the JMS message broker. Following from that the Citrus JMS user has to have the permission to do so. Be sure that the user has the sufficient rights when using temporary reply destinations.

Up to now we have sent a message and waited for a synchronous response in the next step. Now it is also possible to switch the directions of send and receive actions. Then we have the situation where Citrus receives a JMS message first and then Citrus is in charge of providing a proper synchronous response message to the initial sender.

figure_007.jpg

In this scenario the foreign message producer has stored a dynamic JMS reply queue destination to the JMS header. So Citrus has to send the reply message to this specific reply destination, which is dynamic of course. Fortunately the heavy lift is done with the JMS endpoint and we do not have to change anything in our configuration. Again we just define a synchronous message endpoint in the application context.

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.InOut.Queue")
        .build();
}
XML
<citrus-jms:sync-endpoint id="helloServiceSyncEndpoint"
      destination-name="Citrus.HelloService.InOut.Queue"
      timeout="10000"/>

Now the only thing that changes here is that we first receive a message in our test case on this endpoint. The second step is a send message action that references this same endpoint and we are done. Citrus automatically manages the reply destinations for us.

Java
when(receive("helloServiceSyncEndpoint")
        .message()
        .body("..."));

then(send("helloServiceSyncEndpoint")
        .message()
        .body("..."));
XML
<testcase name="jmsSyncMessagingTest">
  <actions>
        <receive endpoint="helloServiceSyncEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </receive>

        <send endpoint="helloServiceSyncEndpoint">
            <message>
                <data>
                  [...]
                </data>
            </message>
        </send>
    </actions>
</testcase>

12.3. JMS topics

Up to now we have used JMS queue destinations on our endpoints. Citrus is also able to connect to JMS topic destinations. In contrary to JMS queues which represents the point-to-point communication JMS topics use publish-subscribe mechanism in order to spread messages over JMS.

A JMS topic producer publishes messages to the topic, while the topic accepts multiple message subscriptions and delivers the message to all subscribers.

The Citrus JMS endpoints offer the attribute 'pub-sub-domain'. Once this attribute is set to true Citrus will use JMS topics instead of queue destinations.

When using JMS topics in your project you may want to configure a javax.jms.TopicConnectionFactory instead of a javax.jms.QueueConnectionFactory.

See the following example where the publish-subscribe attribute is set to true in JMS message endpoint components.

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Topic")
        .pubSubDomain(true)
        .build();
}
XML
<citrus-jms:endpoint id="helloServiceTopicEndpoint"
            destination="Citrus.HelloService.Topic"
            pub-sub-domain="true"/>
----

When using JMS topics you will be able to subscribe several test actions to the topic destination and receive a message multiple times as all subscribers will receive the message. Also other applications besides Citrus are also able to consume messages with a topic subscription. This allows Citrus and other software components to coexist in a test environment.

12.3.1. JMS topic subscriber

By default, Citrus does not deal with durable subscribers when using JMS topics. This means that messages that were sent in advance to the message subscription are not delivered to the Citrus message endpoint. Following from that racing conditions may cause problems when using JMS topic endpoints in Citrus.

Be sure to start the Citrus subscription before messages are sent to the topic. Otherwise, you may lose some messages that were sent in advance to the subscription. By default Citrus will use a subscription per receive action using the JMS endpoint in the test cases. This means that the topic subscription is started and stopped per receive action when the action is performed inside a test case.

In order to solve racing conditions for messages that are sent prior to the subscription you can also use a auto-start setting on the JMS endpoint component. This causes Citrus to start/stop the subscription based on the endpoint lifecycle instead of linking the subscription to the receive action. When the endpoint is ready the subscription is started and all incoming message events are cached and stored to a internal in memory message channel for later consumption in the tests.

Here is the endpoint configuration with auto-start enabled.

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Topic")
        .pubSubDomain(true)
        .autoStart(true)
        .build();
}
XML
<citrus-jms:endpoint id="helloServiceTopicEndpoint"
            destination="helloServiceTopic"
            pub-sub-domain="true"
            auto-start="true"/>
The auto-start option is only valid in combination with pub-sub-domain enabled. Other combinations may be ignored or lead to configuration failure at start-up.

Now with auto-start set to true the Citrus JMS endpoint will setup a subscription at the very beginning when the endpoint is loaded in the project. The internal message channel name is derived from the JMS endpoint id and follows the pattern:

{citrus-jms:endpoint@id}":subscriber.inbound"

The in memory channel id is the combined result of the JMS endpoint id and the prefix :subscriber.inbound. In our example this would be helloServiceTopicEndpoint:subscriber.inbound. Now all messages sent to the topic in advance to the tests are cached and ready for consumption and verification in the test.

In the test nothing really changes for you. You simply use a receive test action on the JMS endpoint as you would have done before. In the background Citrus will automatically receive the messages from the in memory cache. This mechanism enables us to not loose any messages that were sent to the topic in prior to Citrus firing up the test cases.

There is a small downside of the auto-start topic subscriber. As incoming events are cached internally you will not be able to receive the same topic event in multiple receive actions within the Citrus project. If you need to receive the topic message in several places within Citrus you need to set up several JMS topic endpoints with auto-start enabled. In case you just have one receive action at a time you are good to go with the auto-start subscriber as it is described here.

12.4. JMS topic durable subscription

When using durable subscriptions on JMS message brokers the message events on a topic are preserved for a subscriber even if the subscriber is inactive. This means that the subscriber may not loose any message events on that particular topic as the subscription is durable and all events are stored for later consumption.

In case you want to activate durable subscriptions on the Citrus JMS endpoint use the durable-subscription setting in the configuration:

Java
@Bean
public JmsSyncEndpoint helloServiceSyncEndpoint() {
    return new JmsEndpointBuilder()
        .destination("Citrus.HelloService.Topic")
        .pubSubDomain(true)
        .autoStart(true)
        .build();
}

@Bean SingleConnectionFactory topicConnectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
    factory.setBrokerURL("tcp://localhost:61616");
    factory.setClientID("citrusDurableConnectionFactory");
    factory.setWatchTopicAdvisories(false);

    return new SingleConnectionFactory(factory);
}
XML
<citrus-jms:endpoint id="helloServiceTopicEndpoint"
            connection-factory="topicConnectionFactory"
            destination="helloServiceTopic"
            pub-sub-domain="true"
            durable-subscription="true"
            auto-start="true"/>

<bean id="topicConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
  <constructor-arg>
    <bean class="org.apache.activemq.ActiveMQConnectionFactory">
      <property name="brokerURL" value="${jms.broker.url}" />
      <property name="watchTopicAdvisories" value="false"/>
      <property name="clientID" value="citrusDurableConnectionFactory"/>
    </bean>
  </constructor-arg>
</bean>

The durable subscription in Citrus implies that the subscriber is started when the endpoint configuration is done. All messages received on that subscription are cached internally until the receive action in the test case is performed for actual message consumption. The auto-start setting is required to be enabled for this reason when using durable subscriptions.

By default, Citrus is using the JMS endpoint subscriber name as durable subscription name (e.g. helloServiceTopicEndpoint:subscriber). You can overwrite the durable subscriber name with durable-subscriber-name setting on the endpoint.

In addition to that you need to add a client id on the connection factory so the message broker is able to identify the durable subscription with the client address. Also we use the SingleConnectionFactory implementation of Spring as a connection factory wrapper so we do not fail because of multiple connections with the same durable subscriber id.

12.5. JMS message headers

The JMS specification defines a set of special message header entries that can go into your JMS message. These JMS headers are stored differently in a JMS message header than other custom header entries do. This is why these special header values should be set in a special syntax that we discuss in the next paragraphs.

Java
when(receive("helloServiceSyncEndpoint")
        .message()
        .header("citrus_jms_correlationId", "${correlationId}")
        .header("citrus_jms_messageId", "${messageId}")
        .header("citrus_jms_redelivered", "${redelivered}")
        .header("citrus_jms_timestamp", "${timestamp}")
        .body("..."));
XML
<header>
    <element name="citrus_jms_correlationId" value="${correlationId}"/>
    <element name="citrus_jms_messageId" value="${messageId}"/>
    <element name="citrus_jms_redelivered" value="${redelivered}"/>
    <element name="citrus_jms_timestamp" value="${timestamp}"/>
</header>

As you see all JMS specific message headers use the citrus_jms_ prefix. This prefix comes from Spring Integration message header mappers that take care of setting those headers in the JMS message header properly.

Typing of message header entries may also be of interest in order to meet the JMS standards of typed message headers. For instance the following message header is of type double and is therefore transferred via JMS as a double value.

Java
when(receive("jmsEndpoint")
        .message()
        .header("amount", 19.75D)
        .body("..."));
XML
<header>
    <element name="amount" value="19.75" type="double"/>
</header>

12.6. Dynamic destination names

Usually you set the target destination as property on the JMS endpoint component. In some cases it might be useful to set the target destination in a more dynamic way during the test run. You can do this by adding a special message header named citrus_jms_destination_name. This header is automatically interpreted by the Citrus JMS endpoint and is set as the target destination before a message is sent.

Java
when(send("jmsEndpoint")
        .message()
        .header("citrus_jms_destination_name", "dynamic.destination.name")
        .body("..."));
XML
<send endpoint="jmsEndpoint">
    <message>
        ...
    </message>
    <header>
        <element name="citrus_jms_destination_name" value="dynamic.destination.name"/>
    </header>
</send>

This action above will send the message to the destination "dynamic.destination.name" no matter what default destination is set on the referenced endpoint component named jmsEndpoint. The dynamic destination name setting also supports test variables. This means you can use variables and functions in the destination name, too.

Another possibility for dynamic JMS destinations is given