Cucumber BDD support
Behavior driven development (BDD) is becoming more and more popular these days. The idea of defining and describing the software behavior as basis for all tests in prior to translating those feature descriptions into executable tests is a very interesting approach because it includes the technical experts as well as the domain experts. With BDD the domain experts can easily read and verify the tests and the technical experts get a detailed description of what should happen in the test.
The test scenario descriptions follow the Gherkin syntax with a "Given-When-Then" structure most of the time. The Gherkin language is business readable and well known in BDD.
There are lots of frameworks in the Java community that support BDD concepts. Citrus has dedicated support for the Cucumber framework because Cucumber is well suited for extensions and plugins. So with the Citrus and Cucumber integration you can write Gherkin syntax scenario and feature stories in order to execute the Citrus integration test capabilities. As usual we have a look at a first example. First lets see the Citrus cucumber dependency and XML schema definitions.
Note The Cucumber components in Citrus are kept in a separate Maven module. If not already done so you have to include the module as Maven dependency to your project
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-cucumber</artifactId>
<version>2.7</version>
</dependency>
Citrus provides a separate configuration namespace and schema definition for Cucumber related step definitions. Include this namespace into your Spring configuration in order to use the Citrus Cucumber configuration elements. The namespace URI and schema location are added to the Spring configuration XML file as follows.
<spring:beans xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.citrusframework.org/schema/cucumber/testcase"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.citrusframework.org/schema/cucumber/testcase
http://www.citrusframework.org/schema/cucumber/testcase/citrus-cucumber-testcase.xsd">
[...]
</spring:beans>
Cucumber works with both JUnit and TestNG as unit testing framework. You can choose which framework to use with Cucumber. So following from that we need a Maven dependency for the unit testing framework support:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
</dependency>
In order to enable Citrus Cucumber support we need to specify a special object factory in the environment. The most comfortable way to specify a custom object factory is to add this property to the cucumber.properties in classpath.
cucumber.api.java.ObjectFactory=cucumber.runtime.java.CitrusObjectFactory
This special object factory takes care on creating all step definition instances. The object factory is able to inject @CitrusResource annotated fields in step classes. We will see this later on in the examples. The usage of this special object factory is mandatory in order to combine Citrus and Cucumber capabilities.
The CitrusObjectFactory will automatically initialize the Citrus world for us. This includes the default citrus-context.xml Citrus Spring configuration that is automatically loaded within the object factory. So you can define and use Citrus components as usual within your test.
After these preparation steps you are able to combine Citrus and Cucumber in your project.
Cucumber integration
Cucumber is able to run tests with JUnit. The basic test case is an empty test which uses the respective JUnit runner implementation from cucumber.
@RunWith(Cucumber.class)
@CucumberOptions(
plugin = { "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}
The test case above uses the Cucumber JUnit test runner. In addition to that we give some options to the Cucumber execution. We define a special Citrus reporter implementation. This class is responsible for printing the Citrus test summary. This reporter extends the default Cucumber reporter implementation so the default Cucumber report summaries are also printed to the console.
That completes the JUnit class configuration. Now we are able to add feature stories and step definitions to the package of our test MyFeatureIT . Cucumber and Citrus will automatically pick up step definitions and glue code in that test package. So lets write a feature story echo.feature right next to the MyFeatureIT test class.
Feature: Echo service
Scenario: Say hello
Given My name is Citrus
When I say hello to the service
Then the service should return: "Hello, my name is Citrus!"
Scenario: Say goodbye
Given My name is Citrus
When I say goodbye to the service
Then the service should return: "Goodbye from Citrus!"
As you can see this story defines two scenarios with the Gherkin Given-When-Then syntax. Now we need to add step definitions that glue the story description to Citrus test actions. Lets do this in a new class EchoSteps .
public class EchoSteps {
@CitrusResource
protected TestDesigner designer;
@Given("^My name is (.*)$")
public void my_name_is(String name) {
designer.variable("username", name);
}
@When("^I say hello.*$")
public void say_hello() {
designer.send("echoEndpoint")
.messageType(MessageType.PLAINTEXT)
.payload("Hello, my name is ${username}!");
}
@When("^I say goodbye.*$")
public void say_goodbye() {
designer.send("echoEndpoint")
.messageType(MessageType.PLAINTEXT)
.payload("Goodbye from ${username}!");
}
@Then("^the service should return: \"([^\"]*)\"$")
public void verify_return(final String body) {
designer.receive("echoEndpoint")
.messageType(MessageType.PLAINTEXT)
.payload("You just said: " + body);
}
}
If we have a closer look at the step definition class we see that it is a normal POJO that uses a @CitrusResource annotated TestDesigner . The test designer is automatically injected by Citrus Cucumber extension. This is done because we have included the citrus-cucumber dependency to our project before. Now we can write @Given, @When or @Then annotated methods that match the scenario descriptions in our story. Cucumber will automatically find matching methods and execute them. The methods add test actions to the test designer as we are used to it in normal Java DSL tests. At the end the test designer is automatically executed with the test logic.
Important Of course you can do the dependency injection with @CitrusResource annotations on TestRunner instances, too. Which variation should someone use TestDesigner or TestRunner ? In fact there is a significant difference when looking at the two approaches. The designer will use the Gherkin methods to build the whole Citrus test case first before any action is executed. The runner will execute each test action that has been built with a Gherkin step immediately. This means that a designer approach will always complete all BDD step definitions before taking action. This directly affects the Cucumber step reports. All steps are usually marked as successful when using a designer approach as the Citrus test is executed after the Cucumber steps have been executed. The runner approach in contrast will fail the step when the corresponding test action fails. The Cucumber test reports will definitely look different depending on what approach you are choosing. All other functions stay the same in both approaches. If you need to learn more about designer and runner approaches please see
If we run the Cucumber test the Citrus test case automatically performs its actions. That is a first combination of Citrus and Cucumber BDD. The story descriptions are translated to test actions and we are able to run integration tests with behavior driven development. Great! In a next step we will use XML step definitions rather than coding the steps in Java DSL.
Cucumber XML steps
So far we have written glue code in Java in order to translate Gherkin syntax descriptions to test actions. Now we want to do the same with just XML configuration. The JUnit Cucumber class should not change. We still use the Cucumber runner implementation with some options specific to Citrus:
@RunWith(Cucumber.class)
@CucumberOptions(
plugin = { "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}
The scenario description is also not changed:
Feature: Echo service
Scenario: Say hello
Given My name is Citrus
When I say hello to the service
Then the service should return: "Hello, my name is Citrus!"
Scenario: Say goodbye
Given My name is Citrus
When I say goodbye to the service
Then the service should return: "Goodbye from Citrus!"
In the feature package my.company.features we add a new XML file EchoSteps.xml that holds the new XML step definitions:
<?xml version="1.0" encoding="UTF-8"?>
<spring:beans xmlns:citrus="http://www.citrusframework.org/schema/testcase"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.citrusframework.org/schema/cucumber/testcase"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.citrusframework.org/schema/cucumber/testcase
http://www.citrusframework.org/schema/cucumber/testcase/citrus-cucumber-testcase.xsd">
<step given="^My name is (.*)$" parameter-names="username">
<citrus:create-variables>
<citrus:variable name="username" value="${username}"/>
</citrus:create-variables>
</step>
<step when="^I say hello.*$">
<citrus:send endpoint="echoEndpoint">
<citrus:message type="plaintext">
<citrus:data>Hello, my name is ${username}!</citrus:data>
</citrus:message>
</citrus:send>
</step>
<step when="^I say goodbye.*$">
<citrus:send endpoint="echoEndpoint">
<citrus:message type="plaintext">
<citrus:data>Goodbye from ${username}!</citrus:data>
</citrus:message>
</citrus:send>
</step>
<step then="^the service should return: "([^"]*)"$" parameter-names="body">
<citrus:receive endpoint="echoEndpoint">
<citrus:message type="plaintext">
<citrus:data>You just said: ${body}</citrus:data>
</citrus:message>
</citrus:receive>
</step>
</spring:beans>
The above steps definition is written in pure XML. Citrus will automatically read the step definition and add those to the Cucumber runtime. Following from that the step definitions are executed when matching to the feature story. The XML step files follow a naming convention. Citrus will look for all files located in the feature package with name pattern **/.Steps.xml** and load those definitions when Cucumber starts up.
The XML steps are able to receive parameters from the Gherkin regexp matcher. The parameters are passed to the step as test variable. The parameter names get declared in the optional attribute parameter-names . In the step definition actions you can use the parameter names as test variables.
Note The test variables are visible in all upcoming steps, too. This is because the test variables are global by default. If you need to set local state for a step definition you can use another attribute global-context and set it to false in the step definition. This way all test variables and parameters are only visible in the step definition. Other steps will not see the test variables.
Note Another notable thing is the XML escaping of reserved characters in the pattern definition. You can see that in the last step where the then attribute is escaping quotation characters.
then="^the service should return: "([^"]*)"$"
We have to do this because otherwise the quotation characters will interfere with the XML syntax in the attribute.
This completes the description of how to add XML step definitions to the cucumber BDD tests. In a next section we will use predefined steps for sending and receiving messages.
Cucumber Spring support
Cucumber provides support for Spring dependency injection in step definition classes. The Cucumber Spring capabilities are included in a separate module. So we first of all we have to add this dependency to our project:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
</dependency>
The Citrus Cucumber extension has to handle things different when Cucumber Spring support is enabled. Therefore we use another object factory implementation that also support Cucumber Spring features. Change the object factory property in cucumber.properties to the following:
cucumber.api.java.ObjectFactory=cucumber.runtime.java.spring.CitrusSpringObjectFactory
Now we are ready to add @Autowired Spring bean dependeny injection to step definition classes:
@ContextConfiguration(classes = CitrusSpringConfig.class)
public class EchoSteps {
@Autowired
private Endpoint echoEndpoint;
@CitrusResource
protected TestDesigner designer;
@Given("^My name is (.*)$")
public void my_name_is(String name) {
designer.variable("username", name);
}
@When("^I say hello.*$")
public void say_hello() {
designer.send(echoEndpoint)
.messageType(MessageType.PLAINTEXT)
.payload("Hello, my name is ${username}!");
}
@When("^I say goodbye.*$")
public void say_goodbye() {
designer.send(echoEndpoint)
.messageType(MessageType.PLAINTEXT)
.payload("Goodbye from ${username}!");
}
@Then("^the service should return: \"([^\"]*)\"$")
public void verify_return(final String body) {
designer.receive(echoEndpoint)
.messageType(MessageType.PLAINTEXT)
.payload("You just said: " + body);
}
}
As you can see we used Spring autowiring mechanism for the echoEndpoint field in the step definition. Also be sure to define the @ContextConfiguration annotation on the step definition. The Cucumber Spring support loads the Spring application context and takes care on dependency injection. We use the Citrus CitrusSpringConfig Java configuration because this is the main entrance for Citrus test cases. You can add custom beans and further Spring related configuration to this Spring application context. If you want to add more beans for autowiring do so in the Citrus Spring configuration. Usually this is the default citrus-context.xml which is automatically loaded.
Of course you can also use a custom Java Spring configuration class here. But be sure to always import the Citrus Spring Java configuration classes, too. Otherwise you will not be able to execute the Citrus integration test capabilities.
As usual we are able to use @CitrusResource annotated TestDesigner fields for building the Citrus integration test logic. With this extension you can use the full Spring testing power in your tests in particular dependency injection and also transaction management for data persistance tests.
Citrus step definitions
Citrus provides some out of the box predefined steps for typical integration test scenarios. These steps are ready to use in scenario or feature stories. You can basically define send and receive operations. As these steps are predefined in Citrus you just need to write feature stories. The step definitions with glue to test actions are handled automatically.
If you want to enable predefined steps support in your test you need to include the glue code package in your test class like this:
@RunWith(Cucumber.class)
@CucumberOptions(
glue = { "com.consol.citrus.cucumber.step.designer" }
plugin = { "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}
Instead of writing the glue code on our own in step definition classes we include the glue package com.consol.citrus.cucumber.step.designer . This automatically loads all Citrus glue step definitions in this package. Once you have done this you can use predefined steps that add Citrus test logic without having to write any glue code in Java step definitions.
Of course you can also choose to include the TestRunner step definitions by choosing the glue package com.consol.citrus.cucumber.step.runner .
@RunWith(Cucumber.class)
@CucumberOptions(
glue = { "com.consol.citrus.cucumber.step.runner" }
plugin = { "com.consol.citrus.cucumber.CitrusReporter" } )
public class MyFeatureIT {
}
Following basic step definitions are included in this package:
Given variable [name] is "[value]"
Given variables
| [name1] | [value1] |
| [name2] | [value2] |
When <[endpoint-name]> sends "[message-payload]"
Then <[endpoint-name]> should receive (message-type) "[message-payload]"
When <[endpoint-name]> sends
"""
[message-payload]
"""
Then <[endpoint-name]> should receive (message-type)
"""
[message-payload]
"""
When <[endpoint-name]> receives (message-type) "[message-payload]"
Then <[endpoint-name]> should send "[message-payload]"
When <[endpoint-name]> receives (message-type)
"""
[message-payload]
"""
Then <[endpoint-name]> should send
"""
[message-payload]
"""
Once again it should be said that the step definitions included in this package are loaded automatically as glue code. So you can start to write feature stories in Gherkin syntax that trigger the predefined steps. In the following sections we have a closer look at all predefined Citrus steps and how they work.
Variable steps
As you already know Citrus is able to work with test variables that hold important information during a test such as identifiers and dynamic values. The predefined step definitions in Citrus are able to create new test variables.
Given variable messageText is "Hello"
The syntax of this predefined step is pretty self describing. The step instruction follows the pattern:
Given variable [name] is "[value]"
If you keep this syntax in your feature story the predefined step is activated for creating a new variable. We always use the Given step to create new variables.
Scenario: Create Variables
Given variable messageText is "Hello"
And variable operationHeader is "sayHello"
So we can use the And keyword to create more than one variable. Even more comfortable is the usage of data tables:
Given variables
| hello | I say hello |
| goodbye | I say goodbye |
This data table will create the test variable for each row. This is how you can easily create new variables in your Citrus test. As usual the variables are referenced in message payloads and headers as placeholders for dynamically adding content.
Adding variables is usually done within a Scenario block in your feature story. This means that the test variable is used in this scenario which is exactly one Citrus test case. Cucumber BDD also defines a Background block at the very beginning of your Feature . We can also place variables in here. This means that Cucumber will execute these steps for all upcoming scenarios. The test variable is so to speak global for this feature story.
Feature: Variables
Background:
Given variable messageText is "Hello"
Scenario: Do something
Scenario: Do something else
That completes the variable step definitions in Citrus.
Messaging steps
In the previous section we have learned how to use a first predefined Citrus step. Now we want to cover messaging steps for sending and receiving messages in Citrus. As usual with predefined steps you do not need to write any glue code for the steps to take action. The steps are already included in Citrus just use them in your feature stories.
Feature: Messaging features
Background:
Given variable messageText is "Hello"
Scenario: Send and receive plaintext
When <echoEndpoint> sends "${messageText}"
Then <echoEndpoint> should receive plaintext "You just said: ${messageText}"
Of course we need to follow the predefined syntax when writing feature stories in order to trigger a predefined step. Let's have a closer look at this predefined syntax by further describing the above example.
First of all we define a new test variable with Given variable messageText is "Hello" . This tells Citrus to create a new test variable named messageText with respective value. We can do the same for sending and receiving messages like done in our test scenario:
When <[endpoint-name]> sends "[message-payload]"
The step definition requires the endpoint component name and a message payload. The predefined step will automatically configure a send test action in the Citrus test as result.
Then <[endpoint-name]> should receive (message-type) "[message-payload]"
The predefined receive step also requires the endpoint-name and message-payload . As optional parameter you can define the message-type . This is required when sending message payloads other than XML.
This way you can write Citrus tests with just writing feature stories in Gherkin syntax. Up to now we have used pretty simple message payloads in on single line. Of course we can also use multiline payloads in the stories:
Feature: Messaging features
Background:
Given variable messageText is "Hello"
Scenario: Send and receive
When <echoEndpoint> sends
"""
<message>
<text>${messageText}</text>
</message>
"""
Then <echoEndpoint> should receive
"""
<message>
<text>${messageText}</text>
</message>
"""
As you can see we are able to use the send and receive steps with multiline XML message payload data.
Named messages
In the previous section we have learned how to use Citrus predefined step definitions for send and receive operations. The message payload has been added directly to the stories so far. But what is with message header information? We want to specify a complete message with payload and header. You can do this by defining a named message.
As usual we demonstrate this in a first example:
Feature: Named message feature
Background:
Given message echoRequest
And <echoRequest> payload is "Hi my name is Citrus!"
And <echoRequest> header operation is "sayHello"
Given message echoResponse
And <echoResponse> payload is "Hi, Citrus how are you doing today?"
And <echoResponse> header operation is "sayHello"
Scenario: Send and receive
When <echoEndpoint> sends message <echoRequest>
Then <echoEndpoint> should receive message <echoResponse>
In the Background section we introduce named messages echoRequest and echoResponse . This makes use of the new predefined step for adding named message:
Given message [message-name]
Once the message is introduced with its name we can use the message in further configuration steps. You can add payload information and you can add multiple headers to the message. The named message then is referenced in send and receive steps as follows:
When <[endpoint-name]> sends message <[message-name]>
Then <[endpoint-name]> should receive message <[message-name]>
The steps reference a message by its name echoRequest and echoResponse .
As you can see the named messages are used to define complete messages with payload and header information. Of course the named messages can be referenced in many scenarios and steps. Also with usage of test variables in payload and header you can dynmaically adjust those messages in each step.
Message creator steps
In the previous section we have learned how to use named messages as predefined step. The named message has been defined directly in the stories so far. The message creator concept moves this task to some Java POJO. This way you are able to construct more complicated messages for reuse in several scenarios and feature stories.
As usual we demonstrate this in a first example:
Feature: Message creator features
Background:
Given message creator com.consol.citrus.EchoMessageCreator
And variable messageText is "Hello"
And variable operation is "sayHello"
Scenario: Send and receive
When <echoEndpoint> sends message <echoRequest>
Then <echoEndpoint> should receive message <echoResponse>
In the Background section we introduce a message creator EchoMessageCreator in package com.consol.citrus . This makes use of the new predefined step for adding message creators to the test:
Given message creator [message-creator-name]
The message creator name must be the fully qualified Java class name with package information. Once this is done we can use named messages in the send and receive operations:
When <[endpoint-name]> sends message <[message-name]>
Then <[endpoint-name]> should receive message <[message-name]>
The steps reference a message by its name echoRequest and echoResponse . Now lets have a look at the message creator EchoMessageCreator implementation in order to see how this correlates to a real message.
public class EchoMessageCreator {
@MessageCreator("echoRequest")
public Message createEchoRequest() {
return new DefaultMessage("" +
"${messageText}" +
"")
.setHeader("operation", "${operation}");
}
@MessageCreator("echoResponse")
public Message createEchoResponse() {
return new DefaultMessage("" +
"${messageText}" +
"")
.setHeader("operation", "${operation}");
}
}
As you can see the message creator is a POJO Java class that defines one or more methods that are annotated with @MessageCreator annotation. The annotation requires a message name. This is how Citrus will correlate message names in feature stories to message creator methods. The message returned is the used for the send and receive operations in the test. The message creator is reusable accross multiple feature stories and scenarios. In addition to that the creator is able to construct messages in a more powerful way. For instance the message payload could be loaded from file system resources.
Echo steps
Another predefined step definition in Citrus is used to add a echo test action. You can use the following step in your feature scenarios:
Feature: Echo features
Scenario: Echo messages
Given variable foo is "bar"
Then echo "Variable foo=${foo}"
Then echo "Today is citrus:currentDate()"
The step definition requires following pattern:
Then echo "[message]"
Sleep steps
You can add sleep test actions to the feature scenarios:
Feature: Sleep features
Scenario: Sleep default time
Then sleep
Scenario: Sleep milliseconds time
Then sleep 200 ms
The step definition requires one of the following patterns:
Then sleep
Then sleep [time] ms
This adds a new sleep test action to the Citrus test.