Advanced tests with Cucumber

The possibility to configure Cucumber test cases with @CucuomberOptions annotation is not a single one advanced feature. In Cucumber we can also go further and, for example, read Date objects directly from cases descriptions or group similar tests together thanks to tags.

Data Engineering Design Patterns

Looking for a book that defines and solves most common data engineering problems? I'm currently writing one on that topic and the first chapters are already available in πŸ‘‰ Early Release on the O'Reilly platform

I also help solve your data engineering problems πŸ‘‰ contact@waitingforcode.com πŸ“©

This article explores some of advanced features of Cucumber. The first part describes the use of collections in scenario parameters. The second part treats about transforming method parameters to another objects automatically. The third part presents similar conversion concepts using previously described concept: data collections. The next part shows some use cases of dates conversion. The last describes how to group tests together thanks to tags.

Data collections

When given test needs to be verified against a big amount of similar data, it can use different data providers. In Cucumber JVM we don't need to use these objects. Instead, we can simply declare tested collection in features file, as below:

Scenario: Two users with different logins should be registered correctly
    Given Two users try to register:
        | User_1 |
        | User_2 |
    When Two users, User_1 and User_2 want to register:
    Then Both users, User_1 and User_2 should be added in the database:

This scenario contains two definitions of collections: data table (User_1 and User_2 in Given) and inline parameter which will be further converted to collection with @Delimiter(" and ") annotation:

// 4th scenario
@Given("^Two users try to register:")
public void create_users_from_list(List<String> logins) {
  System.out.println(logins);
}

@When("Two users, (.+) want to register:")
public void register_two_users(@Delimiter(" and ") List<String> logins) {
  for (String login : logins) {
    userService.registerUser(new User(login));
  }
}

@Then("Both users, User_1 and User_2 should be added in the database:")
public void check_registering_from_list() {
  assertThat(UserDao.findAll()).hasSize(2);
}

Note that the @Given case doesn't have any parameter declared in RegEx. It's dynamically picked up by Cucumber and converted to appropriated Collection. And List is not a single type supported (Map is another one). The @When case represents another approach because its RegEx contains explicit definition of parameter. Once captured, this parameter is further transformed to List thanks to configuration of delimiter through @Delimiter(" and ") annotation.

Object transformation

Sometimes it's useful to convert tested object directly from features file. Cucumber JVM exposes two possibilities to do so: through @XStreamConverter or @Transform annotations. The first one must be defined at converted class, as for example:

@XStreamConverter(UserConverter.class)
public class User {
  // ...
}

But it makes our code to be coupled to testing layer. To avoid this problematic situation, Cucumber JVM gives another possibility to convert one object to another, through transformers. To transform a String parameter to User instance, we could write following class:

public class UserTransformer extends Transformer<User> {

  @Override
  public User transform(String login) {
    return new User(login);
  }
}

And to apply it on test cases, we must use @Transform annotation as below:

// 5th scenario - Transformer seems to not work very well with Java 8, so we use annotation of Java 7
@Given("^I create user directly from User instance with login ([a-z_1]+)")
public void create_user_instance(@Transform(UserTransformer.class) User user) {
  this.user = user;
}

@When("^login_1 object wants to sign in")
public void register_user_instance() {
  userService.registerUser(user);
}

@Then("^([a-z_1]+) should be added in the database")
public void check_if_user_was_registered_from_instance(String login) {
  assertThat(UserDao.findAll()).hasSize(1);
  assertThat(UserDao.findAll().values()).extracting("login").containsExactly(login);
}

Transpose

Another method that can be used to convert one object to another is expressed thanks to annotation @Transpose. It's based on data tables described in the first part. The idea is quite easy - we define a table in which the first column represents the field of given object and the second column associated value. Let's see it in real example:

Scenario: One user registered from Transpose
    Given I have this user to register
        | login | login1 |
    When I want to register login1 from @Transpose
    Then login1 from @Transpose should be registered correctly

This should be mapped to a List with a single one User, as below:

@Given("^I have this user to register")
public void create_user_with_transpose(@Transpose List<User> users) {
  this.user = users.get(0);
}

@When("^I want to register login1 from Transpose")
public void register_user_from_transpose() {
  userService.registerUser(user);
}

@Then("^([a-z1]+) from Transpose should be registered correctly")
public void check_if_user_from_transpose_was_registered(String login) {
  assertThat(UserDao.findAll()).hasSize(1);
  assertThat(UserDao.findAll().values()).extracting("login").containsExactly(login);
}

The use case looks very much like for data collections. The difference with previous example is that created object is not a single String but the instance of User.

Dates

Another useful utility in Cucumber JVM is dates conversion. It can be done thanks to @Format annotation on date field. There are several different formats supported. The only condition is that the characters used in @Format annotation must be supported by Java java.text.DateFormat. Let's take a small example:

Scenario: Test for formatted dates
    Given I manipulate some date 2030-01-01 00:00:00
    When Add one day
    Then Program should detect changing to 2030-01-02 00:00:00

And associated test scenarios could look like:

@Given("^I manipulate some date (.*)")
public void create_date_from_format(@Format("yyyy-MM-dd HH:mm:ss") Date date) {
  this.date = date;
}

@When("^Add one day")
public void add_one_day() {
  date = Date.from(date.toInstant().plus(1, ChronoUnit.DAYS));
}

@Then("^Program should detect changing to (.*)")
public void check_if_one_day_interval_was_applied(@Format("yyyy-MM-dd HH:mm:ss") Date newDate) {
  assertThat(date).isEqualTo(newDate);
}

Tags

Tagging can be useful when we want to group some features or scenarios and run them in separate testing class. Tag management in Cucumber JVM is quite easy. Tags are represented as Java annotations, beginning with @ character. Tags can be chained, it means that one testing class can execute scenarios or features annotated with one or more tags. Tags defined on features are inherited by all scenarios of given feature. Let's see a declaration example:

Feature: User registration

    We want gain more and more users. So they should be able to register.
    @RegisterOneUser
    Scenario: Successful registration for valid user
        Given I specified my login
        When I want to sign in
        Then My user should be added in database
    @RegisterOneUser
    Scenario: User should see error message when login is not specified
        Given I create user without login
        When I want to sign in
        Then My user shouldn't be added in database

To execute both tests, it's enough to define tags attribute in @CucumberOptions annotation:

@RunWith(Cucumber.class)
@CucumberOptions(tags="@RegisterOneUser")
public class UserServiceTest {
}

Note that if no tags attribute is defined, all scenarios are ran.

In this article we discovered which features can be useful in writing advanced tests with Cucumber. At the begin, we learned about data collections, known also as data tables. They can be used to submit a set of objects to given scenario. Next, we worked on object conversion with @Transform annotation and associated transformers. Thanks to it we can avoid to make the conversion repeatly and manually at the begin of each test. But this is not a single possibility to change convert types. Another one is @Transpose annotation which is based on data tables. The 4th part described almost no-effort date conversion. We terminated this article by describing features and scenarios grouping thanks to tags.


If you liked it, you should read:

πŸ“š Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!