Receiving messages
Just like sending messages the receiving part is a very important action in an integration test. Honestly the receive action is even more important in Citrus as we also want to validate the incoming message contents. We are writing a test so we also need assertions and checks that everything works as expected.
As already mentioned before a message consists of a message header (name-value pairs) and a message payload. Later in this document we will see how to validate incoming messages with payload and header values. We start with a very simple example:
XML DSL
<receive endpoint="helloServiceEndpoint">
<message name="helloRequest">
<payload>
<TestMessage>
<Text>${text}</Text>
</TestMessage>
</payload>
</message>
<header>
<element name="Operation" value="sayHello"/>
<element name="MessageId" value="${messageId}"/>
</header>
</receive>
Overall the receive message action looks quite similar to the send message action. Concepts are identical as we define the message content with payload and header values. 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.
We can use test variables in both message payload an headers. Now let us have a look at the Java DSL representation of this simple example:
Java DSL designer
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.name("helloRequest")
.payload("<TestMessage>" +
"<Text>${text}</Text>" +
"</TestMessage>")
.header("Operation", "sayHello")
.header("MessageId", "${messageId}");
}
Java DSL runner
@CitrusTest
public void messagingTest() {
receive(action -> action.endpoint("helloServiceEndpoint")
.name("helloRequest")
.payload("<TestMessage>" +
"<Text>${text}</Text>" +
"</TestMessage>")
.header("Operation", "sayHello")
.header("MessageId", "${messageId}"));
}
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.
In a good case scenario the message arrives in time and the content can be validated as a next step. This validation can be done in various ways. On the one hand you can specify a whole XML message 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 included into validation.
Besides the message payload Citrus will also perform validation on the received message header values. Test variable usage is supported as usual during the whole validation process for payload 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:
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.
Validate message payloads
The most detailed validation of incoming messages is to define some expected message payload. The Citrus message validator will then perform a detailed message payload comparison. The incoming message has to match exactly to the expected message payload. 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 payload 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 payloads in receive action:
XML DSL
<receive endpoint="helloServiceEndpoint">
<message>
<payload>
<!-- message payload as XML -->
</payload>
</message>
</receive>
<receive endpoint="helloServiceEndpoint">
<message>
<data>
<![CDATA[
<!-- message payload as XML -->
]]>
</data>
</message>
</receive>
<receive endpoint="helloServiceEndpoint">
<message>
<resource file="classpath:com/consol/citrus/messages/TestRequest.xml" />
</message>
</receive>
The three examples above represent three different ways of defining the message payload in a receive message action. On the one hand we can use inline message payloads as nested XML or CDATA sections in the test. On the other hand we can load the message content from external file resource.
Note
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 loose the XSD auto completion features many XML editors offer when constructing your payload.
In Java DSL we also have multiple options for specifying the message payloads:
Java DSL designer
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.payload("<TestMessage>" +
"<Text>Hello!</Text>" +
"</TestMessage>");
}
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.payload(new ClassPathResource("com/consol/citrus/messages/TestRequest.xml"));
}
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.payloadModel(new TestRequest("Hello Citrus!"));
}
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.message(new DefaultMessage("Hello World!")));
}
The examples above represent the basic variations of how to define message payloads in Citrus Java DSL. The payload can be a simple String or a Spring file resource (classpath or file system). In addition to that we can use a model object. When using model objects as payloads we need a proper message marshaller implementation in the Spring application context. By default this is a marshaller bean of type org.springframework.oxm.Marshaller that has to be present in the Spring application context. You can add such a bean for XML and JSON message marshalling for instance.
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.
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.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).
Last not least the message can be defined as Citrus message object. Here you can choose one of the different message implementations used in Citrus for SOAP, Http or JMS messages. Or you just use the default message implementation or maybe a custom implementation.
In general the expected message content can be manipulated using XPath (xpath) or JSONPath (json-path). 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.
Validate message headers
Message headers are used widely in enterprise messaging solution: The message headers are part of the message semantics and need to be validated, too. Citrus can validate message header by name and value.
XML DSL
<receive endpoint="helloServiceEndpoint">
<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-matchers) for a more powerful validation of header values, too.
Let's see how this looks like in Java DSL:
Java DSL designer
@CitrusTest
public void messagingTest() {
receive("helloServiceEndpoint")
.payload("<TestMessage>" +
"<Text>Hello!</Text>" +
"</TestMessage>")
.header("Operation", "sayHello");
}
Java DSL runner
@CitrusTest
public void messagingTest() {
receive(action -> action.endpoint("helloServiceEndpoint")
.payload("<TestMessage>" +
"<Text>Hello!</Text>" +
"</TestMessage>")
.header("Operation", "sayHello"));
}
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.
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:
XML DSL
<selector>
<element> name="correlationId" value="Cx1x123456789"</element>
<element> name="operation" value="getOrders"</element>
</selector>
Java DSL designer
@CitrusTest
public void receiveMessageTest() {
receive("testServiceEndpoint")
.selector("correlationId='Cx1x123456789' AND operation='getOrders'");
}
Java DSL runner
@CitrusTest
public void receiveMessageTest() {
receive(action -> action.endpoint("testServiceEndpoint")
.selector("correlationId='Cx1x123456789' AND operation='getOrders'"));
}
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.
<selector>
<value>
correlationId = 'Cx1x123456789' AND operation = 'getOrders'
</value>
</selector>
Important 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.
Important Previously only JMS message destinations offered support for message selectors! With Citrus version 1.2 we introduced message selector support for Spring Integration message channels, too (see message-channel-selector-support).
Groovy MarkupBuilder
With the Groovy MarkupBuilder you can build XML message payloads in a simple way, without having to write the typical XML overhead. 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 payload XML data we write a Groovy script snippet. The Groovy MarkupBuilder generates the XML message payload with exactly the same result:
XML DSL
<send endpoint="helloServiceEndpoint">
<message>
<builder type="groovy">
markupBuilder.TestMessage {
MessageId('${messageId}')
Timestamp('?')
VersionId('2')
Text('Hello Citrus!')
}
}
</builder>
<element path="/TestMessage/Timestamp"
value="${createDate}"/>
</message>
<header>
<element name="Operation" value="sayHello"/>
<element name="MessageId" value="${messageId}"/>
</header>
</send>
We use the builder element with type groovy and the MarkupBuilder 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 MarkupBuilder 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 MarkupBuilder extension:
- The MarkupBuilder 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 MarkupBuilder script may also be used within receive actions as shown in the following listing:
XML DSL
<send endpoint="helloServiceEndpoint">
<message>
<builder type="groovy" file="classpath:com/consol/citrus/groovy/helloRequest.groovy"/>
</message>
</send>
<receive endpoint="helloServiceEndpoint" timeout="5000">
<message>
<builder type="groovy">
markupBuilder.TestResponse(xmlns: 'http://www.consol.de/schemas/samples/sayHello.xsd'){
MessageId('${messageId}')
CorrelationId('${correlationId}')
User('HelloService')
Text('Hello ${user}')
}
</builder>
</message>
</receive>
As you can see it is also possible to define the script as external file resource. In addition to that namespace support is given as normal attribute definition within the round brackets after the element name.
The MarkupBuilder implementation in Groovy offers great possibilities in defining message payloads. We do not need to write XML tag overhead and we can construct complex message payloads with Groovy logic like iterations and conditional elements. For detailed MarkupBuilder descriptions please see the official Groovy documentation.