Skip to content

Latest commit

 

History

History
458 lines (372 loc) · 10.6 KB

transformers.md

File metadata and controls

458 lines (372 loc) · 10.6 KB

Transformers

Transformers are glue code like step definitions or hooks. You have to define them in your glue classes.

Cucumber allows the following specific transformers:

  • String to any type
  • DocString (multiline string) to any type
  • DataTable to any type
    • transform lines with named headers to any type
    • transform lines without headers to any type
    • transform tables to any type
    • transform cells content to any type

As well as default transformers for:

  • String
  • DataTable
    • lines with named headers
    • cells

String to any

ParameterType annotation allows to transform a String value from a Cucumber expression to a custom type.

It is defined by a name (used in the steps definitions) and a regex. Each group of the regex will map to a parameter of the transformation function.

For instance, the following transformer can be defined:

class Point{
     int x
     int y

    Point(int x, int y) {
        this.x = x
        this.y = y
    }
}

@ParameterType(name = "coordinates", value = "(.+),(.+)")
Point pointTransformer(String x, String y){
    new Point(x.toInteger(), y.toInteger())
}

And used like this:

Given balloon coordinates 123,456 in the game
Given(/balloon coordinates {coordinates} in the game/) { Point point ->
    //Do something
}

Date transformation

For instance, the following transformer can be defined:

@ParameterType(name = "iso8601Date",value = "([0-9]{4})-([0-9]{2})-([0-9]{2})")
LocalDate parameterTypeIso8601Date(String year, String month, String day) {
    LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day))
}
Given today's date is "1971-10-03" and tomorrow is:
"""
1971-10-04
"""
Given('today\'s date is "{iso8601Date}" and tomorrow is:') { LocalDate today, String tomorrow ->
    assertEquals(3, today.getDayOfMonth())
    assertEquals('1971-10-04',tomorrow)
}

DocString to any

DocStringType annotation allows to transform DocString values (multiline string) to a custom type.

For instance, the following transformer can be defined:

class JsonText{
    String json

    JsonText(String json) {
        this.json = json
    }
}

@DocStringType(contentType = "json")
JsonText jsonTransformer(String json){
    new JsonText(json)
}

And used like this:

Given the following json text
"""json
{
"key": "value"
}
"""
Given(/the following json text/) { JsonText jsonText ->
    //Do something
}

DataTable to any

DataTableType allows to transform DataTable to a custom type.

This can be achieved in different ways:

  • transform lines with named headers to any type
  • transform lines without headers to any type
  • transform tables to any type
  • transform cells content to any type

Note that DataTables in Gherkin can not represent null or the empty string unambiguously. Cucumber will interpret empty cells as null. But you can use a replacement to represent empty strings. See below.

See also the Datatable reference.

Lines with named headers

For instance, the following transformer can be defined:

class Author {
    String name
    String surname
    String famousBook

    Author(String name, String surname, String famousBook) {
        this.name = name
        this.surname = surname
        this.famousBook = famousBook
    }
}

@DataTableType
Author authorEntryTransformer(Map<String, String> entry) {
     new Author(
            entry.get("name"),
            entry.get("surname"),
            entry.get("famousBook"))
}

And used like this:

Given the following authors
| name   | surname | famousBook      |
| Alan   | Alou    | The Lion King   |
| Robert | Bob     | Le Petit Prince |
Given(/the following authors/) { List<Author> authors ->
    //Do something with authors
}

// Or using DataTable
Given(/the following authors/) { DataTable table ->
    List<Author> authors = table.asList(Author)
    //Do something with authors
}

Lines without headers

For instance, the following transformer can be defined:

class Author {
    String name
    String surname
    String famousBook

    Author(String name, String surname, String famousBook) {
        this.name = name
        this.surname = surname
        this.famousBook = famousBook
    }
}

@DataTableType
Author authorEntryTransformer(List<String> row) {
     new Author(
            row.get(0),
            row.get(1),
            row.get(2))
}

And used like this:

Given the following authors
| Alan   | Alou    | The Lion King   |
| Robert | Bob     | Le Petit Prince |
Given(/the following authors/) { List<Author> authors ->
    //Do something with authors
}

// Or using DataTable
Given(/the following authors/) { DataTable table ->
    List<Author> authors = table.asList(Author)
    //Do something with authors
}

DataTable

For instance, the following transformer can be defined:

class Author {
    String name
    String surname
    String famousBook

    Author(String name, String surname, String famousBook) {
        this.name = name
        this.surname = surname
        this.famousBook = famousBook
    }
}

class GroupOfAuthors{
    List<Author> authors

    GroupOfAuthors(List<Author> authors) {
        this.authors = authors
    }
}

@DataTableType
GroupOfAuthors authorEntryTransformer(DataTable table) {
    List<Author> authors = table.asMaps().stream()
                                          .map({ entry -> new Author(entry.get("name"), entry.get("surname"), entry.get("famousBook")) })
                                          .collect(Collectors.toList())
     new GroupOfAuthors(authors)
}

Please note that the same transformation could be done using a line transformer. The purpose of this transformer is to show the syntax.

And used like this:

Given the following authors
| name   | surname | famousBook      |
| Alan   | Alou    | The Lion King   |
| Robert | Bob     | Le Petit Prince |
Given(/the following authors/) { DataTable table ->
   def authors = table.convert(GroupOfAuthors,false)
}

Cell

For instance, the following transformer can be defined:

class RichCell{
    String content

    RichCell(String content) {
        this.content = content
    }
}

@DataTableType
RichCell transformCell(String content){
    new RichCell(content)
}

And used like this:

Given the following authors
| Alan   | Alou    | The Lion King   |
| Robert | Bob     | Le Petit Prince |
Given(/the following authors/) { List<List<RichCell>> authors ->
   //Do something with authors
}

// Or using DataTable
Given(/the following authors/) { DataTable table->
    def authors = table.asLists(RichCell)
   //Do something with authors
}

Or with headers like this:

Given the following authors
| name   | surname | famousBook      |
| Alan   | Alou    | The Lion King   |
| Robert | Bob     | Le Petit Prince |
Given(/the following authors/) { List<Map<String,RichCell>> authors ->
   //Do something with authors
}

// Or using DataTable
Given(/the following authors/) { DataTable table->
    def authors = table.asMaps(String,RichCell)
   //Do something with authors
}

Empty values

By default empty values in DataTable are treated as null by Cucumber. If you need to have empty values, you can define a replacement like [empty] that will be automatically replaced to empty when parsing DataTable.

To do so, you can add a parameter to a DataTableType definition.

For instance, with the following definition:

class Author {
    String name
    String surname
    String famousBook

    Author(String name, String surname, String famousBook) {
        this.name = name
        this.surname = surname
        this.famousBook = famousBook
    }
}

@DataTableType(replaceWithEmptyString = "[empty]")
Author authorEntryTransformer(List<String> row) {
    new Author(
            row.get(0),
            row.get(1),
            row.get(2))
}

And the following step:

Given the following authors
| name    | surname | famousBook      |
| Alan    | Alou    | The Lion King   |
| [empty] | Bob     | Le Petit Prince |

You would actually get a list containing Author{name='Alan', surname='Alou', famousBook='The Lion King'} and Author{name='', surname='Bob', famousBook='Le Petit Prince'}.

Default transformers

Default transformers are used when there is no specific transformer.

They can be used with object mappers like Jackson to easily convert from well known strings to objects.

String

For instance, the following definition:

@DefaultParameterTransformer
Object anonymous(String fromValue, Type toValueType) {
    ObjectMapper objectMapper = new ObjectMapper()
    objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}

Will be used to convert with such step definitions:

Given("A step with a undefined {} string") { SomeType someType ->
  // The string between {} will be converted to SomeType
}

DataTable

Lines with named headers

For instance the following definition:

class Author {
    @JsonProperty("name")
    String name
    @JsonProperty("surname")
    String surname
    @JsonProperty("famousBook")
    String famousBook
}
@DefaultDataTableEntryTransformer(replaceWithEmptyString = "[empty]")
Object defaultTableEntryTransformer(Map<String,String> fromValue, Type toValueType) {
    ObjectMapper objectMapper = new ObjectMapper()
    objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}

Will be used to convert with such step definitions:

Given(/the following authors/) { List<Author> authors ->
   //Do something with authors
}

// Or DataTable
Given(/the following authors/) { DataTable table ->
    def authors = table.asList(Author1)
   //Do something with authors
}

Cells

For instance the following definition:

class RichCell{
    String content
    RichCell(String content){
        this.content = content
    }
}

@DefaultDataTableCellTransformer(replaceWithEmptyString = "[empty]")
Object defaultTableEntryTransformer(String fromValue, Type toValueType) {
    ObjectMapper objectMapper = new ObjectMapper()
    objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}

Will be used to convert with such step definitions:

Given(/the following authors/) { List<List<RichCell>> authors ->
   //Do something with authors
}

// Or DataTable
Given(/the following authors/) { DataTable table ->
    def authors = table.asLists(RichCell)
}