YAY, Citrus 3.0 is here! A few weeks ago we have released a new major release for the Citrus integration testing framework! This is a huge step in the Citrus project! It has been over two years since the last release 2.8.0 and the journey towards 3.0 has been a tremendous challenge. Citrus is back and stronger than ever before!
This summary is here to share our strategy, ideas and all major changes that are part of Citrus 3.0!
Objectives
Citrus 3.0 is a major release, and we want to take that as an opportunity to follow up with some improvements and refactoring that we are eager to do for quite some time. That being said we try to comply with everybody’s need to migrate from older versions. People coming from Citrus 2.x should have a look at the 2.x migration guide. In addition to that we take extra care to keep breaking changes on a low-level.
Here are the main objectives we have in Citrus 3.0
- Modularize Citrus
- Improved Java DSL
- Make Spring optional
- Update dependencies to new major versions (Cucumber, Apache Camel, Spring Framework, …)
Modularize Citrus
The citrus-core
module is the heart of the framework and contains all capabilities that Citrus has to offer.
So if you include citrus-core
as a dependency in your project you will load a lot of artifacts as transitive dependencies (e.g. from Maven central).
Loading that huge amount of libraries is not a good thing especially when you do not need all features provided by Citrus (e.g. Groovy script support, Xhtml, XML validation and so on).
With citrus-core
it is all or nothing. So we are keen to modularize the core module into several smaller pieces.
The user can then choose which of the Citrus modules and features to include into the project or even overwrite and substitute certain pieces with own implementations.
Module categories and structure
In Citrus 3.0 we end up with following module categories:
-
API and base implementations of core Citrus features. There will be a separate
citrus-base
andcitrus-spring
module where latter encapsulates the Spring Framework support in Citrus (more about that in make Spring optional).Module Description citrus-api Interfaces, enums, constants citrus-base Default implementation of citrus-api
citrus-spring Adds Spring Framework support to citrus-base
(Bean definition parsers, Application context configuration, Autowiring in factory beans -
Test execution modules such as JUnit, TestNG and Cucumber representing different ways to run Citrus tests.
Module Description citrus-cucumber Run Citrus tests as Cucumber BDD feature files citrus-testng Run tests via TestNG unit test framework citrus-junit Run tests via JUnit4 unit test framework citrus-junit5 Run tests via JUnit5 unit test framework citrus-main Run tests via Java main CLI citrus-groovy Run Citrus tests as Groovy scripts (to be continued …) -
Endpoints connect Citrus to a message transport like Apache Kafka, JMS, Http REST, Ftp, Mail and many more. Each endpoint may provide producer/consumer or client/server components to exchange message content over the respective transport.
Module Description citrus-camel Interact with Apache Camel context, routes and control bus citrus-ftp Connect to and simulate FTP/SFTP servers citrus-http Http REST support citrus-jdbc Simulate JDBC drivers, connections and transactions citrus-jms Publish/consume messages on a JMS message broker citrus-kafka Exchange data via Kafka messaging citrus-jmx Call MBean operations and simulate MBeans citrus-mail Client and server side SMTP mail support citrus-rmi Call RMI via JNDI registry lookup and simulate RMI services citrus-ssh Connect to servers via SSH and simulate SSH servers citrus-vertx Exchange messages on the Vert.x event bus citrus-websocket Websocket support citrus-ws SOAP WebServices support including SOAP envelope handling, WSDL, WS-Security, … citrus-zookeeper Connect with Zookeeper servers citrus-spring-integration Exchange messages on Spring Integration message channels -
When Citrus receives messages the test case is eager to verify the message content. Validation modules implement message validators and mechanisms to validate different data formats such as Json, XML, plaintext, binary content and so on. Some validation modules also add support for verification tools such as Groovy script validation, Hamcrest and AssertJ.
Module Description citrus-validation-xml XML, Xpath and Xhtml message validation citrus-validation-json Json and JsonPath message validation citrus-validation-text Plain text message validation citrus-validation-binary Validate binary message content using input streams or base64
encodingcitrus-validation-groovy Adds Groovy script validation for XML, Json, SQL result set citrus-validation-hamcrest Hamcrest matcher support like assertThat(oneOf(is(foo), is(foobar)))
-
Connectors are similar to endpoints yet these components connect Citrus to a foreign technology or framework rather than implementing a message transport. Connectors typically provide a client side only implementation that enable Citrus to interact with a service or framework (e.g. Docker, Kubernetes, Selenium web driver).
Module Description citrus-sql Connect with a relational database citrus-docker Connect with Docker deamon to manage images and containers citrus-selenium Connect with web driver to run web-based UI tests citrus-kubernetes Connect to Kubernetes cluster managing PODs services and other resources -
Tooling is important and the modules in this category provide little helpers and plugins for different use cases where the usage of Citrus needs to be simplified (e.g. Maven plugins, test generators, etc.)
Module Description citrus-restdocs Auto generate request/response documentation for Http REST and SOAP communication citrus-maven-plugin Maven plugins to create tests citrus-archetypes Maven archetypes for project code generation citrus-test-generator Create and auto generate test cases (e.g. from Swagger OpenAPI specifications) -
A catalog in Citrus combines several other modules into a set of modules that usually get used together. The
citrus-core
module for instance combines all available validation modules, runtimes and the Citrus Spring support into a single artifact. So the user just needs to addcitrus-core
to the project and can use everything Citrus has to offer (exactly like Citrus 2.x is doing).Module Description citrus-bom Bill of material holding all modules for imports citrus-core Default Citrus capabilities (validation, runtime, Spring support) combined into one single module (exactly the same what you have had with previous versions) citrus-endpoint-catalog Combine all endpoints to a single source for endpoint builders -
We are about to take a major step in Citrus and this implies some backward incompatibilities that “vintage” modules try to solve for users that still need to stick with an older version of Citrus for some reason. With these “vintage” modules you can still run older test cases with the new Citrus 3.x code base.
Module Description citrus-java-dsl Old Java DSL implementation (designer vs. runner) to be used for Citrus 2.x Java DSL tests citrus-arquillian Arquillian runtime for Citrus -
Module in the utility category provide tooling for internal usage only. For instance this is a shared test library that is used in unit testing by several other modules. The modules are only used when building the Citrus modules. Utility modules usually are not included in a release so they won’t be pushed to Maven central.
Module Description citrus-test-support Internal helper library added as test scoped dependency for unit testing in other modules. Holds shared unit testing helpers.
How to use the new module structure
Users that do not want to change much in their project regarding the dependency setup just continue to add citrus-core
dependency.
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-core</artifactId>
<version>1.1.0</version>
</dependency>
This will get you the same capabilities as in Citrus 2.x with all validation modules, runtime and Spring support enabled. The citrus-core
is a catalog module combining several other modules that get automatically added to your project.
The downside of this approach is that you get a lot of features and transitive dependencies that you might not need in your project. Fortunately you can exclude some features from citrus-core
with the new module structure in 3.x.
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-core</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-validation-groovy</artifactId>
</exclusion>
<exclusion>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-testng</artifactId>
</exclusion>
</exclusions>
</dependency>
The example above excludes the Groovy validation capabilities and the TestNG runtime from the project. The features will not be added to your project and fewer artifacts get downloaded.
Of course there is a lot more to exclude and you might end up having a more complicated configuration for all those exclusions.
For people trying to operate with just what they need in their project the pull approach might be the way to go. Here you add just citrus-base
as dependency.
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-base</artifactId>
<version>1.1.0</version>
</dependency>
If you want to use Spring Framework support you may also add:
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-spring</artifactId>
<version>1.1.0</version>
</dependency>
With the new modular setup in Citrus not every feature is enabled by default. As you write and execute tests in your project you might then run into errors because you are using a Citrus feature that has not yet been added to your project. Something like:
FAILURE: Caused by: NoSuchValidationMatcherException: Can not find validation matcher "assertThat" in library citrusValidationMatcherLibrary ()
at com/consol/citrus/jms/integration/JmsTopicDurableSubscriberIT(iterate:26-48)
The error indicates that you need to add the Hamcrest validation matcher feature to the project. You can do so by adding the respective module dependency in your project:
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-validation-hamcrest</artifactId>
<version>1.1.0</version>
</dependency>
The awesomeness about it is that you can add your favorite matcher implementation as a dependency (we still need to add AssertJ support in Citrus, so we would love a contribution doing that!).
What happened to citrus-*-model modules?
Each module in former Citrus versions has had a little brother that generated model classes from XSD schema files.
The XSD schemas are used for custom Spring bean definition parsing and were located in the citrus-*-model
modules (e.g. citrus-config.xsd).
The initial idea behind that separate model module was to separate model classes from implementations in order to use that model in a user interface called citrus-admin
.
With Citrus 3.x we included the XSD schemas into the implementation modules so we do not have to maintain all the citrus-*-model
modules separately (also one less artifact to load, excellent).
Java DSL
Citrus provides a Java domain specific language to write integration tests with a fluent API. The API makes use of the fluent builder pattern to specify test actions.
In Citrus 2.x all Java DSL related test action builders were located in a separate module called citrus-java-dsl
.
For better maintainability and modularization reasons the test action builders have moved into the individual modules
where the test action implementation is located.
In fact the Java DSL builders are now inner classes of the respective test action.
In former releases users had to choose between the two different approaches to write Java DSL tests with this fluent API:
Just like many things in life both approaches have had individual advantages and downsides. We have accepted the challenge to combine both approaches designer and runner into a single approach that combines the advantages and minimizes downsides.
In Citrus 3.x we end up using a simplified Java DSL that uses the look and feel of the former designer API but executes each step immediately to keep debugging options and the capability to add custom code between steps.
The separation between designer and runner has been removed completely. So there is only one single source of truth the
TestCaseRunner
and the fluent Java API for writing tests in Citrus.
This simplifies the implementation in other modules (Cucumber, TestNG, JUnit) a lot.
This is how a new Java DSL test looks like in Citrus 3.x:
public class HelloServiceIT extends TestNGCitrusSpringSupport {
@Autowired
private HttpClient httpClient;
@Autowired
private KafkaEndpoint orderEvents;
@Test
@CitrusTest
public void test() {
given(variable("orderId", 1000));
when(http().client(httpClient)
.send()
.post("/orders")
.contentType(APPLICATION_FORM_URLENCODED)
.body("order=${orderId}&name=foo"));
then(receive(orderEvents)
.body("Order ${orderId} has been placed"));
and(http().client(httpClient)
.receive()
.response(HttpStatus.OK));
}
}
The test extends TestNGCitrusSpringSupport
.
This gives you the annotation support for @CitrusTest
so the test is added to the Citrus test reporting.
The base class also gives you the test action execution methods given()
, when()
, then()
and and()
.
This relates to the BDD Gherkin language and is widely known to a lot of people out there.
If you do not want to use this BDD approach in your test you can also use the basic run()
method or its shortcut version $()
instead.
$(http().client(httpClient)
.send()
.post("/orders")
.contentType(APPLICATION_FORM_URLENCODED)
.body("order=${orderId}&name=foo"));
Former Citrus versions provided many base classes which confused users.
The classes TestNGCitrusSupport
/JUnit4CitrusSupport
are now the single base class for all tests including XML and Java DSL tests.
The JUnit 5 support provides a @CitrusSupport
extension annotation.
@CitrusSpringSupport
@ContextConfiguration(classes = {CitrusSpringConfig.class})
public class HelloServiceIT {
@Autowired
private HttpClient httpClient;
@Autowired
private KafkaEndpoint orderEvents;
@Test
@CitrusTest
public void test(@CitrusResource GherkinTestActionRunner $) {
$.given(variable("orderId", 1000));
$.when(http().client(httpClient)
.send()
.post("/orders")
.contentType(APPLICATION_FORM_URLENCODED)
.body("order=${orderId}&name=foo"));
$.then(receive(orderEvents)
.body("Order ${orderId} has been placed"));
$.and(http().client(httpClient)
.receive()
.response(HttpStatus.OK));
}
}
Make Spring optional
The Spring framework is a wide spread and well appreciated framework for Java applications. The framework provides an awesome set of projects, libraries and tools. The dependency injection and IoC concepts introduced with Spring are groundbreaking.
Some people prefer to choose other approaches though to work with dependency injection. Others do struggle with mastering Citrus and Spring as new frameworks at the same time. Both frameworks Spring and Citrus are very powerful and newbies sometimes feel overwhelmed with having to deal with so much new stuff at the same time.
In former releases Citrus has been very tied to Spring and in some cases this has been a showstopper to work with Citrus for mentioned reasons.
In Citrus 3.x we make Spring optional in core
modules so people can choose to enable/disable Spring support.
In particular this affects the way Citrus components are started and linked to each other via the Spring application context.
Citrus and Spring
When Spring is enabled for Citrus all components are loaded with a Spring application context. This enables autowiring and bean definition parsing. Latter bean definition parsing for custom components is mandatory when using XML based configuration and XML test cases in Citrus.
Users enable the Spring support in Citrus by adding the following module:
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-spring</artifactId>
<version>1.1.0</version>
</dependency>
When using citrus-core
dependency this Spring support is enabled by default in order to adjust with what has been configured in previous Citrus versions.
Citrus standalone
In case you exclude the citrus-spring
module for Citrus you will load the same components and features but only without Spring framework support.
Keep in mind only the XML based configuration and XML test cases continue to require Spring.
In non-Spring mode custom components can be directly configured in the Citrus context then.
Also Citrus uses a resource common path lookup mechanism to identify common components that get loaded automatically.
So you simply add components such as citrus-validation-json
to your project classpath and the Json validation capabilities are loaded automatically.
The resource path lookup is a mechanism to identify components in Citrus that should be loaded automatically when the Citrus application is started. You only need to add components to the classpath (e.g. by adding a Maven dependency) and the resource gets loaded automatically. This mechanism is used to decouple modules and to provide a non-Spring mode for Citrus.
Feel free to choose which approach fits best for your needs. Citrus running with or without Spring.
Update dependencies
It has been quite some time since the last major Citrus release. So this is a point where we catch up with all the other libraries and dependencies that evolved over time. In particular this is Apache Camel, Spring and Cucumber that all evolved with major versions in the past.
The following might be the most important updates:
- Java 11
- Spring framework 5.3
- Apache Camel 3.9
- TestNG 7.1
- JUnit 5.7
- Jetty 9.4
- Kafka 2.8
- Selenium 3.141
- Log4J2 2.14
- Cucumber 6.10
What’s next!?
So 3.0 is the first version of the Citrus 3.x release train. And we are not done yet! We continue to work on our goals to simplifying the ways of writing integration tests so Citrus is ready for the future challenges of software testing.
We love to get feedback so please give it a try and tell us what you think about Citrus 3.0. Now is the time to raise your voice to improve the framework!