Friday, October 11, 2013

5 Simple Object Marshaling and Transformation Techniques in Cucumber-JVM

There's not a lot of blog posts, or formal documentation, on some of the cooler tricks you can do to marshal objects from DataTables or extracted strings in Gherkin expressions.  Since I'm working with Cucumber-JVM a lot these days, I thought I would share 5 simples marshaling/transformation techniques I've discovered that will help you clean up those Step Definitions.

1.  Implicit Type Conversion.

Cucumber can convert strings to integer, double, or boolean values simply by specifying primitive values in the function signature of a step definition.

Scenario: An example of implicit type conversion

  When I need numbers or booleans

  Then sometimes it's easier to let Cucumber convert values 1 or 1.1 or true instead of me

//...
@Then("^sometimes it's easier to let Cucumber convert values (\\d+)" 
       + "or ((?:\\d|\\.)+) or (true|false) instead of me$")
public void sometimes_it_s_easier_to_let_cucumber_convert_values(
        int integer, double decimal, boolean bool) {

  assertEquals(1, integer);
  assertEquals(1.1d, decimal, 0);
  assertTrue(bool);
}
//...

2.  Implicit Conversion to a List<String>.

Cucumber can also convert comma separated strings into a List<String> by specifying a List<String> function argument in a step definition.

Scenario: An example of implicit conversion of lists

  When I need a bunch of items

  Then sometimes it's easier to deal with a list: apples, bananas, oranges

//...
@Then("^sometimes it's easier to deal with a list: ((?:\\s|\\w|,)+)$")
public void sometimes_its_easier_to_deal_with_a_lists(List<String> list) {

  assertTrue(
    list.containsAll(
      Arrays.asList("apples", "oranges", "bananas")));
}
//...

3.  Explicitly Convert a Date or Calendar object using a Formatter.

Some strings are a little more complex or arbitrary to parse, so you can help Cucumber by telling it the format of the string to parse.  By default, Cucumber supports Date and Calendar strings, but the JavaDoc alludes to other possibilities.

Scenario: Convert more complex values using special formats

  When I need to do something with dates

  Then I should be able to use 10/31/2013 or 10/31/2013 12:32:22 and get a Date object back

//...
@Then("^I should be able to use ((?:\\d|\\/)+) or ((?:\\d|\\/|:|\\s)+)" 
      + "and get a Date object back$")
public void I_should_be_able_to_use_or_AM_and_get_a_Date_object_back(
        @Format("MM/dd/yyyy") Calendar actualCalendar1,
        @Format("MM/dd/yyyy HH:mm:ss") Calendar actualCalendar2)  {

  Calendar expectedCalendar1 = Calendar.getInstance();
  expectedCalendar1.set(2013, 9, 31, 0, 0, 0);

  Calendar expectedCalendar2 = Calendar.getInstance();
  expectedCalendar2.set(2013, 9, 31, 12, 32, 22);

  assertEquals(
    expectedCalendar1.getTime().getYear(), 
    actualCalendar1.getTime().getYear());
  assertEquals(
    expectedCalendar1.getTime().getMonth(), 
    actualCalendar1.getTime().getMonth());
  assertEquals(
    expectedCalendar1.getTime().getDate(), 
    actualCalendar1.getTime().getDate());

  assertEquals(
    expectedCalendar2.getTime().getYear(), 
    actualCalendar2.getTime().getYear());
  assertEquals(
    expectedCalendar2.getTime().getMonth(), 
    actualCalendar2.getTime().getMonth());
  assertEquals(
    expectedCalendar2.getTime().getDate(), 
    actualCalendar2.getTime().getDate());
  assertEquals(
    expectedCalendar2.getTime().getHours(), 
    actualCalendar2.getTime().getHours());
  assertEquals(
    expectedCalendar2.getTime().getMinutes(), 
    actualCalendar2.getTime().getMinutes());
  assertEquals(
    expectedCalendar2.getTime().getSeconds(), 
    actualCalendar2.getTime().getSeconds());
}
//...

4.  Explicitly Convert a String to a <T> (strongly-typed object).

Cucumber provides a method of using a Transformer object to translate a string to a strongly typed object.  One only has to extend the Transformer abstract class, and use the @Transform annotation in the step definition's signature.

Scenario: Transform something more complex using a custom transform

  When I need to work with IP Addresses or Phone Numbers

  Then I should be able to parse 127.0.0.1 or 555-555-5555 and get custom objects back

//...
@Then("^I should be able to parse (\\d+(?:[.]\\d+){3}) or" 
      + " (\\d+(?:-\\d+){2}) and get custom objects back$")
public void I_should_be_able_to_parse_or_and_get_custom_objects_back(
        @Transform(IPAddressTransformer.class) InetAddress ipAddress,
        @Transform(PhoneNumberTransformer.class) PhoneNumber phoneNumber) {

  assertTrue(ipAddress.isLoopbackAddress());

  assertEquals(555, phoneNumber.getAreaCode());
  assertEquals(555, phoneNumber.getPrefix());
  assertEquals(5555, phoneNumber.getLineNumber());
}
//...

5.  Convert a DataTable to a List<T>.

Finally, there's a nice mechanism for converting a DataTable into a List<T>, where <T> is a strongly-typed object represented by each table row.  The top row is considered a header row; the column names should be the name of a primitive property on your object type.  Cucumber will smartly handle the header, so you don't need to use camel-casing ("firstName" can be "First Name" as the column name).

Another nice feature Cucumber offers is an implicit conversion of the DataTable to the List<T> (specified in the function signature).  Alternatively, if you want access to the DataTable, you can accept it as an argument and call dataTable.asList(Type type) to manually perform the conversion.

Scenario: Transform a data table into a list of strongly typed objects

  When I need to specify a lot of data as a table

  Then I should be able to get a list of real objects back:
    | First Name | Last Name | Age | Is Male |
    | Obi-Wan    | Kenobi    | 55  | true    |
    | Han        | Solo      | 35  | true    |
    | Luke       | Skywalker | 24  | true    |
    | Leia       | Organa    | 24  | false   |

//...
@Then("^I should be able to get a list of real objects back:$")
public void I_should_be_able_to_get_a_list_of_real_objects_back(List<Person> persons)  {

  assertEquals(4, persons.size());
}
//...


Well, I hope this helped.  In a future post, I will talk about some of the lessons I've learned in using Cucumber-JVM, particularly around it's effective use in a complex project.

You can find the source (in the example3 packages) on Github:  https://github.com/berico-rclayton/Cucumber-JVM-Examples.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.