Skip to content

Migration guide: Citrus 2.x to 3.x

Christoph Deppisch edited this page Sep 11, 2023 · 1 revision

Our work on Citrus 3.0 has begun and this page is here to collect migration steps that users need to perform when upgrading from former Citrus versions.

Table of contents


Citrus settings

Default settings and constants have been moved to a new utility class named CitrusSettings.

Citrus 2.x

Citrus.DEFAULT_MESSAGE_TYPE

Citrus 3.0

CitrusSettings.DEFAULT_MESSAGE_TYPE

Channel endpoints

The Spring integration message channel endpoint is no longer the default endpoint implementation used by server components. The new default is the DirectEndpoint.

Because of this we have moved the Spring integration message channel endpoint implementation from citrus-core to a separate module called citrus-spring-integration.

<dependency>
  <groupId>com.consol.citrus</groupId>
  <artifactId>citrus-spring-integration</artifactId>
  <version>${citrus.version}</version>
</dependency>

When you keep using citrus-core as dependency in your project it already includes the new citrus-spring-integration module as dependency so you do not need to adjust anything here.

In case you are using the XML Spring bean configuration in Citrus you have to change the XML namespaces though.

Citrus 2.x

<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">

  <citrus:channel-endpoint id="fooEndpoint"
                           channel="foo"/>

  <citrus:channel-sync-endpoint id="fooSyncEndpoint"
                           channel="foo"/>

  <citrus:channel id="foo" capacity="5"/>

</beans>

Citrus 3.0

<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-si="http://www.citrusframework.org/schema/spring-integration/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
                         http://www.citrusframework.org/schema/spring-integration/config http://www.citrusframework.org/schema/spring-integration/config/citrus-spring-integration-config.xsd">

  <citrus-si:channel-endpoint id="fooEndpoint"
                           channel="foo"/>

  <citrus-si:channel-sync-endpoint id="fooSyncEndpoint"
                           channel="foo"/>

  <citrus-si:channel id="foo" capacity="5"/>

</beans>

Notice that the components have been moved out of the default namespace xmlns:citrus="http://www.citrusframework.org/schema/config" to a new one xmlns:citrus-si="http://www.citrusframework.org/schema/spring-integration/config".

As an alternative you can also use the new direct endpoint implementation that is now located in the default Citrus configuration namespace.

New DirectEndpoint

<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">

  <citrus:direct-endpoint id="fooEndpoint"
                           queue="foo"/>

  <citrus:direct-sync-endpoint id="fooSyncEndpoint"
                           queue="foo"/>

  <citrus:queue id="foo"/>

</beans>

TestReporter

The test reporter interface has changed in its signature and way of working.

Citrus 2.x

public interface TestReporter {
    /**
     * Test reporter generates a report for several test suite instances.
     */
    void generateTestResults();
    
    /**
     * Dismiss previous test results for next test run.
     */
    void clearTestResults();
}

Citrus 3.0

public interface TestReporter {

    /**
     * Test reporter generates a report for several test suite instances.
     */
    void generateReport(TestResults testResults);
}

In former Citrus versions the reporter had to also implement TestListener, TestActionListener and TestSuiteListener interfaces in order to collect the test results on its own. This resulted in all reporters actually doing the same to get all test results for report generation.

We have refactored this so the reporter is provided with the test results when generating the report. The test results are now collected in a single central place.

In case you have a custom test reporter you may need to adjust to this new API.


Cucumber BDD

We have updated from Cucumber 3.x to 5.x and there are some changes due to this major update. You need to change the cucumber.properties to select the new object factory implementation:

Citrus 2.x

cucumber.api.java.ObjectFactory=cucumber.runtime.java.CitrusObjectFactory

Citrus 3.0

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

Also all custom steps need to use the new TestCaseRunner API:

Citrus 2.x

public class TodoSteps {

    @CitrusResource
    private TestRunner runner;

    @Given("^Todo list is empty$")
    public void empty_todos() {
        runner.http(httpActionBuilder -> httpActionBuilder
            .client("todoListClient")
            .send()
            .delete("/api/todolist"));

        runner.http(httpActionBuilder -> httpActionBuilder
            .client("todoListClient")
            .receive()
            .response(HttpStatus.OK));
    }
}

Citrus 3.0

public class TodoSteps {

    @CitrusResource
    private TestCaseRunner runner;

    @Given("^Todo list is empty$")
    public void empty_todos() {
        runner.given(http()
            .client("todoListClient")
            .send()
            .delete("/api/todolist"));

        runner.then(http()
            .client("todoListClient")
            .receive()
            .response(HttpStatus.OK));
    }
}

Sleep action

The sleep test action builder in Java DSL has changed. In former versions the type (Long or Double) of the given method parameter specified the time in milliseconds or seconds.

Citrus 2.x

sleep(500L); //500 milliseconds
sleep(2.5D); //2.5 seconds

In Citrus 3.0 you specify the amount of time to sleep with separate methods.

Citrus 3.0

sleep().milliseconds(500L);
sleep().seconds(2.5D);
sleep().time(Duration.ofMillis(5000));
sleep().time("${days}", TimeUnit.DAYS);

Hamcrest matcher

In former Citrus versions you were able to use Hamcrest matchers directly as condition value in an iterating container.

Citrus 2.x

import static org.hamcrest.Matchers.lessThanOrEqualTo;

@Test
@CitrusTest
public void testHamcrestCondition() {
    iterate()
        .condition(lessThanOrEqualTo(5))
        .actions(
            createVariable("todoId", "citrus:randomUUID()"),
            createVariable("todoName", "todo_${i}"),
            createVariable("todoDescription", "Description: ${todoName}")
        );
}

With Citrus 3.0 the dependency to Hamcrest features is optional so you can also choose another implementation (e.g. AssertJ). This leads us to changing the supported condition value that you can pass into the container. There is a new ConditionExpression wrapper that holds the actual matcher implementation.

Citrus 3.0

import static com.consol.citrus.container.HamcrestConditionExpression.assertThat;
import static org.hamcrest.Matchers.lessThanOrEqualTo;

@Test
@CitrusTest
public void testHamcrestCondition() {
    iterate()
        .condition(assertThat(lessThanOrEqualTo(5)))
        .actions(
            createVariable("todoId", "citrus:randomUUID()"),
            createVariable("todoName", "todo_${i}"),
            createVariable("todoDescription", "Description: ${todoName}")
    );
}

Endpoint adapter configuration

Endpoint adapters are commonly used in server components to handle incoming requests and provide proper response messages. Each server component has an endpoint adapter configured by default. In case you want to overwrite the endpoint adapter you can do this in a Spring bean configuration for instance.

Citrus 2.x

@Bean
public EndpointAdapter todoResponseAdapter() {
    StaticResponseEndpointAdapter endpointAdapter = new StaticResponseEndpointAdapter();
    endpointAdapter.setMessagePayload("{" +
                        "\"id\": \"${todoId}\"," +
                        "\"title\": \"${todoName}\"," +
                        "\"description\": \"${todoDescription}\"," +
                        "\"done\": false" +
                    "}");
    return endpointAdapter;
}

In former Citrus versions the endpoint adapter has had some autowired components like the TestContextFactory. As we have moved Spring autowiring to factory beans in a separate module you can no longer use variable support in the endpoint adapter as shown above with ${todoId} in the response payload data. In order to add support for the variables we need to explicitly set the TestContextFactory:

Citrus 3.0

@Bean
public EndpointAdapter todoResponseAdapter(TestContextFactory contextFactory) {
    StaticResponseEndpointAdapter endpointAdapter = new StaticResponseEndpointAdapter();
    endpointAdapter.setMessagePayload("{" +
                        "\"id\": \"${todoId}\"," +
                        "\"title\": \"${todoName}\"," +
                        "\"description\": \"${todoDescription}\"," +
                        "\"done\": false" +
                    "}");
    endpointAdapter.setTestContextFactory(contextFactory);
    return endpointAdapter;
}

You simply need to set the TestContextFactory on the endpoint adapter. As an alternative you can use the factory bean for the endpoint adapter where autowiring of Spring beans is enabled as it was done before in previous Citrus versions.

@Bean
public StaticResponseEndpointAdapterFactory todoResponseAdapter() {
    StaticResponseEndpointAdapterFactory endpointAdapterFactory = new StaticResponseEndpointAdapterFactory();
    endpointAdapterFactory.setMessagePayload("{" +
                        "\"id\": \"${todoId}\"," +
                        "\"title\": \"${todoName}\"," +
                        "\"description\": \"${todoDescription}\"," +
                        "\"done\": false" +
                    "}");
    return endpointAdapterFactory;
}

Moved classes

Class Description From To
Functions Citrus functions catalog class combining all available default functions to be called in Java DSL com.consol.citrus.dsl.functions com.consol.citrus.functions