Arquillian support
Arquillian is a well known integration test framework that comes with a great feature set when it comes to Java EE testing inside of a full qualified application server. With Arquiliian you can deploy your Java EE services in a real application server of your choice and execute the tests inside the application server boundaries. This makes it very easy to test your Java EE services in scope with proper JNDI resource allocation and other resources provided by the application server. Citrus is able to connect with the Arquillian test case. Speaking in more detail your Arquillian test is able to use a Citrus extension in order to use the Citrus feature set inside the Arquillian boundaries.
Read the next section in order to find out more about the Citrus Arquillian extension.
Citrus Arquillian extension
Arquillian offers a fine mechanism for extensions adding features to the Arquillian test setup and test execution. The Citrus extension respectively adds Citrus framework instance creation and Citrus test execution to the Arquillian world. First of all lets have a look at the extension descriptor properties settable via arquillian.xml :
<extension qualifier="citrus">
<property name="citrusVersion">2.6.1</property>
<property name="autoPackage">true</property>
<property name="suiteName">citrus-arquillian-suite</property>
</extension>
The Citrus extension uses a specific qualifier citrus for defining properties inside the Arquillian descriptor. Following properties are settable in current version:
- citrusVersion: The explicit version of Citrus that should be used. Be sure to have the same library version available in your project (e.g. as Maven dependency). This property is optional.
By default the extension just uses the latest stable version.
- autoPackage: When true (default setting) the extension will automatically add Citrus libraries and all transitive dependencies to the test deployment. This automatically enables you to use the Citrus API inside the Arquillian test
even when the test is executed inside the application container.
- suiteName: This optional setting defines the name of the test suite that is used for the Citrus test run. When using before/after suite functionality in Citrus this setting might be of interest.
- configurationClass: Full qualified Java class name of customized Citrus Spring bean configuration to use when loading the Citrus Spring application context. As a user you can define a custom configuration class that must
be a subclass of com.consol.citrus.config.CitrusSpringConfig. When specified the custom class is loaded otherwise the default com.consol.citrus.config.CitrusSpringConfig is loaded to set up the Spring application context.
Now that we have added the extension descriptor with all properties we need to add the respective Citrus Arquillian extension as library to our project. This is done via Maven in your project's POM file as normal dependency:
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-arquillian</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>
Now everything is set up to use Citrus within Arquillian. Lets use Citrus functionality in a Arquillian test case.
Client side testing
Arquillian separates client and container side testing. When using client side testing the test case is executed outside of the application container deployment. This means that your test case has no direct access to container managed resources such as JNDI resources. On the plus side it is not necessary to include your test in the container deployment. The test case interacts with the container deployment as a normal client would do. Lets have a look at a first example:
@RunWith(Arquillian.class)
@RunAsClient
public class EmployeeResourceTest {
@CitrusFramework
private Citrus citrusFramework;
@ArquillianResource
private URL baseUri;
private String serviceUri;
@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(RegistryApplication.class, EmployeeResource.class,
Employees.class, Employee.class, EmployeeRepository.class);
}
@Before
public void setUp() throws MalformedURLException {
serviceUri = new URL(baseUri, "registry/employee").toExternalForm();
}
@Test
@CitrusTest
public void testCreateEmployeeAndGet(@CitrusResource TestDesigner designer) {
designer.send(serviceUri)
.message(new HttpMessage("name=Penny&age=20")
.method(HttpMethod.POST)
.contentType(MediaType.APPLICATION_FORM_URLENCODED));
designer.receive(serviceUri)
.message(new HttpMessage()
.statusCode(HttpStatus.NO_CONTENT));
designer.send(serviceUri)
.message(new HttpMessage()
.method(HttpMethod.GET)
.accept(MediaType.APPLICATION_XML));
designer.receive(serviceUri)
.message(new HttpMessage("" +
"" +
"20" +
"Penny" +
"" +
"")
.statusCode(HttpStatus.OK));
citrusFramework.run(designer.build());
}
}
First of all we use the basic Arquillian JUnit test runner @RunWith(Arquillian.class) in combination with the @RunAsClient annotation telling Arquillian that this is a client side test case. As this is a usual Arquillian test case we have access to Arquillian resources that automatically get injected such as the base uri of the test deployment. The test deployment is a web deployment created via ShrinkWrap. We add the application specific classes that build our remote RESTful service that we would like to test.
The Citrus Arquillian extension is able to setup a proper Citrus test environment in the background. As a result the test case can reference a Citrus framework instance with the @CitrusFramework annotation. We will use this instance of Citrus later on when it comes to execute the Citrus testing logic.
No we can focus on writing a test method which is again nothing but a normal JUnit test method. The Citrus extension takes care on injecting the @CitrusResource annotated method parameter. With this Citrus test designer instance we can build a Citrus test logic for sending and receiving messages via Http in order to call the remote RESTful employee service of our test deployment. The Http endpoint uri is injected via Arquillian and we are able to call the remote service as a client.
The Citrus test designer provides Java DSL methods for building the test logic. Please note that the designer will aggregate all actions such as send or receive until the designer is called to build the test case with build() method invocation. The resulting test case object can be executed by the Citrus framework instance with run() method.
When the Citrus test case is executed the messages are sent over the wire. The respective response message is received with well known Citrus receive message logic. We can validate the response messages accordingly and make sure the client call was done right. In case something goes wrong within Citrus test execution the framework will raise exceptions accordingly. As a result the JUnit test method is successful or failed with errors coming from Citrus test execution.
This is how Citrus and Arquillian can interact in a test scenario where the test deployment is managed by Arquillian and the client side actions take place within Citrus. This is a great way to combine both frameworks with Citrus being able to call different service API endpoints in addition with validating the outcome. This was a client side test case where the test logic was executed outside of the application container. Arquillian also supports container remote test cases where we have direct access to container managed resources. The following section describes how this works with Citrus.
Container side testing
In previous sections we have seen how to combine Citrus with Arquillian in a client side test case. This is the way to go for all test cases that do not need to have access on container managed resources. Lets have a look at a sample where we want to gain access to a JMS queue and connection managed by the application container.
@RunWith(Arquillian.class)
public class EchoServiceTest {
@CitrusFramework
private Citrus citrusFramework;
@Resource(mappedName = "jms/queue/test")
private Queue echoQueue;
@Resource(mappedName = "/ConnectionFactory")
private ConnectionFactory connectionFactory;
private JmsSyncEndpoint jmsSyncEndpoint;
@Deployment
@OverProtocol("Servlet 3.0")
public static WebArchive createDeployment() throws MalformedURLException {
return ShrinkWrap.create(WebArchive.class)
.addClasses(EchoService.class);
}
@Before
public void setUp() {
JmsSyncEndpointConfiguration endpointConfiguration = new JmsSyncEndpointConfiguration();
endpointConfiguration.setConnectionFactory(new SingleConnectionFactory(connectionFactory));
endpointConfiguration.setDestination(echoQueue);
jmsSyncEndpoint = new JmsSyncEndpoint(endpointConfiguration);
}
@After
public void cleanUp() {
closeConnections();
}
@Test
@CitrusTest
public void shouldBeAbleToSendMessage(@CitrusResource TestDesigner designer) throws Exception {
String messageBody = "ping";
designer.send(jmsSyncEndpoint)
.messageType(MessageType.PLAINTEXT)
.message(new JmsMessage(messageBody));
designer.receive(jmsSyncEndpoint)
.messageType(MessageType.PLAINTEXT)
.message(new JmsMessage(messageBody));
citrusFramework.run(designer.build());
}
private void closeConnections() {
((SingleConnectionFactory)jmsSyncEndpoint.getEndpointConfiguration().getConnectionFactory()).destroy();
}
}
As you can see the test case accesses two container managed resources via JNDI. This is a JMS queue and a JMS connection that get automatically injected as resources. In a before test annotated method we can use these resources to build up a proper Citrus JMS endpoint. Inside the test method we can use the JMS endpoint for sending and receiving JMS messages via Citrus. As usual response messages received are validated and compared to an expected message. As usual we use the Citrus TestDesigner method parameter that is injected by the framework. The designer is able to build Citrus test logic with Java DSL methods. Once the complete test is designed we can build the test case and run the test case with the framework instance. After the test we should close the JMS connection in order to avoid exceptions when the application container is shutting down after the test.
The test is now part of the test deployment and is executed within the application container boundaries. As usual we can use the Citrus extension to automatically inject the Citrus framework instance as well as the Citrus test builder instance for building the Citrus test logic.
This is how to combine Citrus and Arquillian in order to build integration tests on Java EE services in a real application container environment. With Citrus you are able to set up more complex test scenarios with simulated services such as mail or ftp servers. We can build Citrus endpoints with container managed resources.
Test runners
In the previous sections we have used the Citrus TestDesigner in order to construct a Citrus test case to execute within the Arquillian boundaries. The nature of the test designer is to aggregate all Java DSL method calls in order to build a complete Citrus test case before execution is done via the Citrus framework. This approach can cause some unexpected behavior when mixing the Citrus Java DSL method calls with Arquillian test logic. Lets describe this by having a look at an example where th mixture of test designer and pure Java test logic causes unseen problems.
@Test
@CitrusTest
public void testDesignRuntimeMixture(@CitrusResource TestDesigner designer) throws Exception {
designer.send(serviceUri)
.message(new HttpMessage("name=Penny&age=20")
.method(HttpMethod.POST)
.contentType(MediaType.APPLICATION_FORM_URLENCODED));
designer.receive(serviceUri)
.message(new HttpMessage())
.statusCode(HttpStatus.NO_CONTENT));
Employee testEmployee = employeeService.findEmployee("Penny");
employeeService.addJob(testEmployee, "waitress");
designer.send(serviceUri)
.message(new HttpMessage()
.method(HttpMethod.GET)
.accept(MediaType.APPLICATION_XML));
designer.receive(serviceUri)
.message(new HttpMessage("" +
"" +
"20" +
"Penny" +
"" +
"waitress" +
"" +
"" +
""))
.statusCode(HttpStatus.OK));
citrusFramework.run(designer.build());
}
As you can see in this example we create a new Employee named Penny via the Http REST API on our service. We do this with Citrus Http send and receive message logic. Once this is done we would like to add a job description to the employee. We use a service instance of EmployeeService which is a service of our test domain that is injected to the Arquillian test as container JEE resource. First of all we find the employee object and then we add some job description using the service. Now as a result we would like to receive the employee as XML representation via a REST service call with Citrus and we expect the job description to be present.
This combination of Citrus Java DSL methods and service call logic will not work with TestDesigner . This is because the Citrus test logic is not executed immediately but aggregated to the very end where the designer is called to build the test case. The combination of Citrus design time and Java test runtime is tricky.
Fortunately we have solved this issue with providing a separate TestRunner component. The test runner provides nearly the same Java DSL methods for constructing Citrus test logic as the test designer. The difference though is that the test logic is executed immediately when calling the Java DSL methods. So following from that we can mix Citrus Java DSL code with test runtime logic as expected. See how this looks like with our example:
@Test
@CitrusTest
public void testDesignRuntimeMixture(@CitrusResource TestRunner runner) throws Exception {
runner.send(new BuilderSupport<SendMessageBuilder>() {
@Override
public void configure(SendMessageBuilder builder) {
builder.endpoint(serviceUri)
.message(new HttpMessage("name=Penny&age=20")
.method(HttpMethod.POST)
.contentType(MediaType.APPLICATION_FORM_URLENCODED));
}
});
runner.receive(new BuilderSupport<ReceiveMessageBuilder>() {
@Override
public void configure(ReceiveMessageBuilder builder) {
builder.endpoint(serviceUri)
.message(new HttpMessage()
.statusCode(HttpStatus.NO_CONTENT));
}
});
Employee testEmployee = employeeService.findEmployee("Penny");
employeeService.addJob(testEmployee, "waitress");
runner.send(new BuilderSupport<SendMessageBuilder>() {
@Override
public void configure(SendMessageBuilder builder) {
builder.endpoint(serviceUri)
.message(new HttpMessage()
.method(HttpMethod.GET)
.accept(MediaType.APPLICATION_XML));
}
});
runner.receive(new BuilderSupport<ReceiveMessageBuilder>() {
@Override
public void configure(ReceiveMessageBuilder builder) {
builder.endpoint(serviceUri)
.message(new HttpMessage("" +
"" +
"20" +
"Penny" +
"" +
"waitress" +
"" +
"" +
"")
.statusCode(HttpStatus.OK));
}
});
}
The test logic has not changed significantly. We use the Citrus TestRunner as method injected parameter instead of the TestDesigner . And this is pretty much the trick. Now the Java DSL methods do execute the Citrus test logic immediately. This is why the syntax of the Citrus Java DSL methods have changed a little bit. We now use a anonymous interface implementation for constructing the send/receive test action logic. As a result we can use the Citrus Java DSL as normal code and we can mix the runtime Java logic as each statement is executed immediately.
With Java 8 lambda expressions our code looks even more straight forward and less verbose as we can skip the anonymous interface implementations. With Java 8 you can write the same test like this:
@Test
@CitrusTest
public void testDesignRuntimeMixture(@CitrusResource TestRunner runner) throws Exception {
runner.send(builder -> builder.endpoint(serviceUri)
.message(new HttpMessage("name=Penny&age=20")
.method(HttpMethod.POST)
.contentType(MediaType.APPLICATION_FORM_URLENCODED));
runner.receive(builder -> builder.endpoint(serviceUri)
.message(new HttpMessage()
.statusCode(HttpStatus.NO_CONTENT));
Employee testEmployee = employeeService.findEmployee("Penny");
employeeService.addJob(testEmployee, "waitress");
runner.send(builder -> builder.endpoint(serviceUri)
.message(new HttpMessage()
.method(HttpMethod.GET)
.accept(MediaType.APPLICATION_XML));
runner.receive(builder -> builder.endpoint(serviceUri)
.message(new HttpMessage("" +
"" +
"20" +
"Penny" +
"" +
"waitress" +
"" +
"" +
"")
.statusCode(HttpStatus.OK));
}