Enterprise Application Integration testing with Citrus Framework

Last week i read a thread on the Oracle forums on how to test the Oracle Service Bus processes.
A comment from one of the users lead to me this framework, Citrus Framework.

Citrus is an integration testing framework written in Java that tests your software application to fit into your customer’s environment. The tool simulates
surrounding systems across various transports (e.g. Http, JMS, TCP/IP, SOAP, …) in order to perform automatic end-to-end use case testing.

For one of my customers we had a lot of unit tests for testing all the flows in the middleware. Different scenarios, different protocols, eventually all needed to be tested.
Constructing all theses possible scenarios, we build everything from scratch to be able to send messages on different protocols and do all the correlation of the messages which go in and which eventually arrive at target destination.

To simulate a one of these scenarios i wanted to try the Citrus Framework to see what it could do for me.

For my test i used the greetings sample which ships with the citrus-1.1-SNAPSHOT distribution.

Directory structure
Create the directory structure as desribed over here.
When all done you should have a structure like this

  • src/citrus/java – location of the generated Testng testcases
  • src/citrus/resoruces – location of all the configuration needed for the framework
  • src/citrus/tests – location of all the defined testcases
  • optional – lib – location of all the dependency libraries
  • optional – log – location in which citrus framework will store logging (payload fragments)
  • build.xml – ant build script

Example of the used build.xml

<project name="greetings" basedir="." default="citrus.run.tests">

	<property file="src/citrus/resources/citrus.properties"/>

    <path id="citrus-classpath">
        <pathelement path="src/citrus/java"/>
    	<fileset dir="lib">
            <include name="*.jar"/>
        </fileset>
    </path>

	<typedef resource="citrustasks" classpath="lib/citrus-ant-tasks-1.1-SNAPSHOT.jar"/>

	<target name="compile.tests">
	    <javac srcdir="src/citrus/java" classpathref="citrus-classpath"/>
		<javac srcdir="src/citrus/tests" classpathref="citrus-classpath"/>
	</target>

	<target name="citrus.run.tests" depends="compile.tests" description="Runs all Citrus tests">
		<citrus suitename="${testsuite.name}" package="com.consol.citrus.samples.*"/>
	</target>

	<target name="citrus.run.single.test" depends="compile.tests" description="Runs a single test by name">
   	 	<touch file="test.history"/>

    	<loadproperties srcfile="test.history"/>

    	<echo message="Last test executed: ${last.test.executed}"/>

    	<input message="Enter test name:" addproperty="testclass" defaultvalue="${last.test.executed}"/>

    	<propertyfile file="test.history">
			<entry key="last.test.executed" type="string" value="${testclass}"/>
		</propertyfile>

		<citrus suitename="citrus-samples" test="${testclass}"/>
    </target>

	<target name="create.test" description="Creates a new empty test case">
		<input message="Enter test name:" addproperty="test.name"/>
        <input message="Enter test description:" addproperty="test.description" defaultvalue="TODO: Description"/>
        <input message="Enter author's name:" addproperty="test.author" defaultvalue="${default.test.author}"/>
        <input message="Enter package:" addproperty="test.package" defaultvalue="${default.test.package}"/>
		<input message="Enter framework:" addproperty="test.framework" defaultvalue="testng"/>

		<java classname="com.consol.citrus.util.TestCaseCreator">
            <classpath refid="citrus-classpath"/>
			<arg line="-name ${test.name} -author ${test.author} -description ${test.description} -package ${test.package} -framework ${test.framework}"/>
        </java>
    </target>

	<target name="create.html.doc" description="Creates test documentation in html">
        <mkdir dir="target"/>

        <java classname="com.consol.citrus.doc.HtmlTestDocGenerator">
            <classpath refid="citrus-classpath" />

            <arg value="src/citrus/tests"/>
            <arg value="target/CitrusTests.html"/>
            <arg value="Citrus Test Documentation"/>
            <arg value="logo.png"/>
            <arg value="Overview"/>
        </java>

        <copy todir="target" file="src/citrus/resources/logo.png"/>

        <zip destfile="target/CitrusTests.zip">
            <fileset dir="target">
                <include name="CitrusTests.html"/>
                <include name="logo.png"/>
            </fileset>
        </zip>
    </target>

    <target name="create.xls.doc" description="Creates test documentation in excel">
        <mkdir dir="target"/>

        <java classname="com.consol.citrus.doc.ExcelTestDocGenerator">
            <classpath refid="citrus-classpath" />

            <arg value="src/citrus/tests"/>
            <arg value="CitrusTests"/>
            <arg value="Citrus Test Documentation"/>
            <arg value="Citrus Testframework"/>
            <arg value="ConSol* Software GmbH"/>
        </java>
    </target>
</project>

Scenario
Middleware gets delivered a new message on a queue. Some process picks up the message, does some transformation/validation on it, and passes it on. Eventually a new message will get sended to some other queue.

– Pre-Requirements

  • weblogic jms queue (citrus_queue_in, citrus_queue_out)
  • weblogic connection factory (TestCF)
  • wlfullclient.jar (cd WL_HOME/server/lib, java -jar wljarbuilder.jar)
  • available process flow for testing (i used a simple Oracle Service Bus (OSB)) flow which received a message on citrus_queue_in and routes the mesage to citrus_queue_out)

First we need to config src/citrus/resources/citrus-context.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:citrus="http://www.citrusframework.org/schema/config"
       xmlns:citrus-ws="http://www.citrusframework.org/schema/ws/config"
       xmlns:citrus-http="http://www.citrusframework.org/schema/http/config"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.citrusframework.org/schema/config http://www.citrusframework.org/schema/config/citrus-config-1.1.xsd">

	<!-- Common settings -->
	<bean id="schemaRepository" class="com.consol.citrus.xml.XsdSchemaRepository">
		<property name="schemas">
			<list>
				<bean class="org.springframework.xml.xsd.SimpleXsdSchema">
					<property name="xsd" value="classpath:com/consol/citrus/samples/greeting/schema/greeting.xsd"/>
				</bean>
			</list>
		</property>
	</bean>

	<bean class="com.consol.citrus.variable.GlobalVariables">
		<property name="variables">
			<map>
				<entry key="project.name" value="Citrus Greeting sample"/>
			</map>
		</property>
	</bean>

	<bean class="com.consol.citrus.aop.StoreMessageInterceptorAspect"/>

	<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
		<property name="environment">
			<props>
				<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
				<prop key="java.naming.provider.url">t3://localhost:7001</prop>
			</props>
		</property>
	</bean>

	<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiTemplate">
			<ref bean="jndiTemplate" />
		</property>
		<property name="jndiName">
			<value>TestCF</value>
		</property>
	</bean>

	<bean id="jmsDestinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver">
		<property name="jndiTemplate">
			<ref bean="jndiTemplate" />
		</property>
		<property name="cache">
			<value>true</value>
		</property>
	</bean>

	<bean id="sendGreeting" class="com.consol.citrus.jms.JmsMessageSender">
		<property name="destinationName" value="citrus_queue_in"/>
		<property name="connectionFactory">
			<ref bean="connectionFactory" />
		</property>
		<property name="destinationResolver">
			<ref bean="jmsDestinationResolver" />
		</property>
	</bean>

	<bean id="receiveGreeting" class="com.consol.citrus.jms.JmsMessageReceiver">
		<property name="destinationName" value="citrus_queue_out"/>
		<property name="connectionFactory">
			<ref bean="connectionFactory" />
		</property>
		<property name="destinationResolver">
			<ref bean="jmsDestinationResolver" />
		</property>
	</bean>
<!--
	<citrus:jms-message-sender id="getOrdersRequestSender"
						       destination-name="testJMSServer/citrus_queue_in"/>

	<citrus:jms-message-receiver id="getOrdersResponseReceiver"
								 destination-name="testJMSServer/citrus_queue_out"/>
-->
	<!-- TestSuite definition -->
	<bean name="citrus-samples-greeting" class="com.consol.citrus.TestSuite"/>
</beans>

Let’s discuss a few parts

  • bean id=”schemaRepository” class=”com.consol.citrus.xml.XsdSchemaRepository”
  • Registrate the xsd for the message payload, which eventually will be used to validate the messages
  • bean class=”com.consol.citrus.variable.GlobalVariables”
  • Setup some global settings
  • bean class=”com.consol.citrus.aop.StoreMessageInterceptorAspect”
  • Activate extra debugging
  • bean id=”jndiTemplate, connectionFactory and jmsDestinationResolver”
  • Configuration for the lookup of the weblogic resources
  • bean id=”sendGreeting” class=”com.consol.citrus.jms.JmsMessageSender”
  • used for sending the messages to the jms queue
  • bean id=”receiveGreeting” class=”com.consol.citrus.jms.JmsMessageReceiver”
  • used for receiving the messages of the jms queue

** Note. Default configuration describes to make use of :

<citrus:jms-message-sender id="getOrdersRequestSender" destination-name="testJMSServer/citrus_queue_in"/>

This gave me errors about Spring/Citrus not able to find the destination queue.
In case of weblogic resources use the “bean id-….” definition. (thanks to Christoph of the Consol team for helping me out on this one)

Secondly we need to config src/citrus/tests/com/consol/citrus/samples/greeting/jms/GreetingJmsTest.xml

<?xml version="1.0" encoding="UTF-8"?>
<spring:beans xmlns="http://www.citrusframework.org/schema/testcase" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.citrusframework.org/schema/testcase http://www.citrusframework.org/schema/testcase/citrus-testcase.xsd">
	<testcase name="GreetingJmsTest">
		<meta-info>
			<author>Eric Elzinga</author>
			<creationdate>2010-03-25</creationdate>
			<status>FINAL</status>
			<last-updated-by>Eric Elzinga</last-updated-by>
			<last-updated-on>2010-03-28T00:00:00</last-updated-on>
		</meta-info>

		<variables>
			<variable name="correlationId" value="citrus:randomNumber(10)"></variable>
			<variable name="user" value="Eric"></variable>
		</variables>

		<actions>
			<send with="sendGreeting">
			<message>
				<resource file="classpath:xmlData/greeting_request.xml" />
			</message>
			<header>
				<element name="Operation" value="sayHello"/>
				<element name="CorrelationId" value="${correlationId}"/>
			</header>
		</send>

		<receive with="receiveGreeting">
		<selector>
			<value>CorrelationId = '${correlationId}'</value>
		</selector>
		<message>
		  <resource file="classpath:xmlData/greeting_response.xml" />
		  <ignore path="//tns:GreetingRequestMessage/tns:Text" />
		</message>
		<header>
			<element name="Operation" value="sayHello"/>
			<element name="CorrelationId" value="${correlationId}"/>
		</header>
	</receive>
</actions>
</testcase>
</spring:beans>

**Notes

  • resource vs data tag. To include the payload of the test we can either use
    <message>
    	<resource file="classpath:xmlData/greeting_request.xml" />
    </message>
    

    or

    <message>
    	<data>
    		<![CDATA[
    		   <tns:GreetingRequestMessage xmlns:tns="http://www.citrusframework.org/samples/greeting">
    			   <tns:CorrelationId>${correlationId}</tns:CorrelationId>
    			   <tns:Operation>sayHello</tns:Operation>
    			   <tns:User>${user}</tns:User>
    			   <tns:Text>Hello Citrus!</tns:Text>
    		   </tns:GreetingRequestMessage>
    		]]>
    	</data>
    </message>
    
  • header tag adds/validates header information (soap headers/jms properties/etc)
  • selector tag gives us the option to let the framework only select the message we would expect there to be.
    If we were testing against a ‘running’ environment, it could be possible that other processes are also sending messages to queue. With the selector tag we only select the message from the queue which succeed the criteria.

    <selector>
    	<value>CorrelationId = '${correlationId}'</value>
    </selector>
    

    If we skip this selector, put some messages in the out-queue and do the test again we will see the next error message :

    [citrus] Caused by: java.lang.IllegalArgumentException: Values not equal for header element 'CorrelationId', expected '2243051261' but was '2791756984'
    

    So it will try to match the first message it will find in the queue.

  • the ignore tag will give us the option to select elements the framework should not validate
    elements which consist of current datetime values are an example of these
  • full validation vs partial validation
  • in the example included i validate the whole xml message. in case we only want to validate/check a few elements from the message we can use the next :

    <message>
    	<validate path="//tns:GreetingRequestMessage/tns:Operation" value="sayHello"/>
    </message>
    

    The result
    Run the ant buildfile, ant citrus.run.tests

       [citrus] 4547   INFO  port.LoggingReporter| TEST FINISHED: GreetingJmsTest
       [citrus] 4547   INFO  port.LoggingReporter| ------------------------------------------------------------------------
       [citrus] 4563   INFO  port.LoggingReporter| FINISH TESTSUITE citrus-samples-greeting
       [citrus] 4563   INFO  port.LoggingReporter| ------------------------------------------------------------------------
       [citrus] 4563   INFO  port.LoggingReporter| ________________________________________________________________________
       [citrus] 4563   INFO  port.LoggingReporter|
       [citrus] 4563   INFO  port.LoggingReporter| CITRUS TEST RESULTS
       [citrus] 4563   INFO  port.LoggingReporter|
       [citrus] 4563   INFO  port.LoggingReporter|  GreetingJmsTest ............................................... SUCCESS
       [citrus] 4563   INFO  port.LoggingReporter|
       [citrus] 4563   INFO  port.LoggingReporter| Total number of tests: 1
       [citrus] 4563   INFO  port.LoggingReporter| Skipped: 0 (0.0%)
    

    Successful result!

    What we have tested in here :
    – Are we able to put a message on the in-queue, and get a response back on the out-queue.
    – Does the response xml validates against the supplied schema definition (xsd)
    – Do the response queue message also have the correct header properties

    Other features
    Ant/Maven support
    Database access/validation
    Adapters (tibco/ws/http)
    … and more

    In a following blog item i will try to simulate some other scenario to see if the framework covers it all.
    Original blog

Share this Post:
Digg Google Bookmarks reddit Mixx StumbleUpon Technorati Yahoo! Buzz DesignFloat Delicious BlinkList Furl

2 Responses to “Enterprise Application Integration testing with Citrus Framework”

[…] Posted in osb, testing by Eric Elzinga on April 12, 2010 Before reading on, read my first article on the Citrus Framework. In this second part of testing by use of the Citrus Framework i want to […]

[…] reading on, read my first article on the Citrus Framework. In this second part of testing by use of the Citrus Framework i want to […]

Leave a Reply:

Name (required):
Mail (will not be published) (required):
Website:
Comment (required):
XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>