Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List of Java Objects to Cucumber DataTable to perform diff

Let us have the following feature file,

Feature: Search Employees

  Background: 
    Given following employees exists
      | id | name   | department | 
      | 1  | Jack   | HR         | 
      | 2  | Rachel | Finance    | 
      | 3  | Mike   | HR         | 
      | 4  | Emma   | IT         | 

  Scenario: Get Employees By Department

    Given user wants to get list employees in a department

     When searched for department = 'HR'

     Then following list of employees are returned
      | id | name | department | 
      | 1  | Jack | HR         | 
      | 3  | Mike | HR         | 

Imagine, following step calls a REST endpoint which returns a JSON.

When searched for department = 'HR'

Here is the repose JSON,

[
  {
    "id": 1,
    "name": "Jack",
    "department": "HR"
  },
  {
    "id": 3,
    "name": "Mike",
    "department": "HR"
  }
]

Corresponding Java Class,

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Employee {

  private Integer id;
  private String name;
  private String department;

}

In older version of cucumber (ie 1.2.4), we can do DataTable.diff(List<Map<String, String> actual) as below,

@Then("^following list of employees are returned$")
public void following_list_of_employees_are_returned(DataTable expectedEmployees) throws Throwable {

  List<Map<String, Object>> actualEmployees = new ArrayList<>();
  List<Employee> employees = response.as(Employee[].class);

  employees
      .forEach(e -> {
        Map<String, Object> map = new HashMap<>();
        map.put("id", e.getId());
        map.put("name", e.getName());
        map.put("department", e.getDepartment());

        actualEmployees.add(map);
      });

  expectedEmployees.unorderedDiff(actualEmployees);
}

Currently, we upgraded to following cucumber version,

<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-java8</artifactId>
  <version>4.0.0</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-spring</artifactId>
  <version>4.0.0</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-junit</artifactId>
  <version>4.0.0</version>
  <scope>test</scope>
</dependency>

[INFO] +- io.cucumber:cucumber-java8:jar:4.0.0:test
[INFO] |  +- io.cucumber:cucumber-java:jar:4.0.0:test
[INFO] |  \- net.jodah:typetools:jar:0.5.0:test
[INFO] +- io.cucumber:cucumber-spring:jar:4.0.0:test
[INFO] \- io.cucumber:cucumber-junit:jar:4.0.0:test
[INFO]    \- io.cucumber:cucumber-core:jar:4.0.0:test
[INFO]       +- io.cucumber:cucumber-html:jar:0.2.7:test
[INFO]       +- io.cucumber:gherkin:jar:5.1.0:test
[INFO]       +- io.cucumber:tag-expressions:jar:1.1.1:test
[INFO]       +- io.cucumber:cucumber-expressions:jar:6.1.0:test
[INFO]       \- io.cucumber:datatable:jar:1.1.3:test
[INFO]          \- io.cucumber:datatable-dependencies:jar:1.1.3:test

PROBLEM: In cucumber 1.2.4 versions, DataTable can be diff'ed with a List<Map<String, String>. In the newer version (4.0.0), DataTable.diff expects a DataTable as argument and there is no method to support diff'ing List.

Now, we need to create a datatable object from List<Map<String, String>. so that we can do expectedDataTable.diff(actualDataTable).

QUESTION: Is there a easy way to convert Array of JSON Object or List<JavaObject> to a DataTable so that we can do diff of 2 datatables without creating List<List<String>> from list of objects which requires a lot of code.

like image 643
Arun Chandrasekaran Avatar asked Nov 27 '18 16:11

Arun Chandrasekaran


People also ask

Which cucumber method is used for data comparison in data table?

You can compare data table with another data in tabular format. This data can be from excel, database or even from the REST services.

How do you pass data from a feature file in cucumber?

When we have multiple test data to pass in a single step of a feature file, one way is to pass multiple parameters and another way is to use Data Tables. Data Tables is a data structure provided by cucumber. It helps you to get data from feature files to Step Definitions.

What is the difference between DataTable and examples in cucumber?

Use Example Table where ENTIRE scenario needs to be tested with different/multiple test data. Use Data table where test data is Explicitly meant for specific steps and user would like to interpret based on use case internally.


1 Answers

Full disclosure: I wrote the data table module for Cucumber.

Manually mapping objects from and to data tables is time consuming, boring and error prone. This is best left to an object mapper. Additionally the implementation used to compare a table to List<Map<String, String>> contained magic, flamethrowers and gotchas. So I thought it best to leave it out.

Solution 1

The first thing you want to do is upgrade to v4.2.0.

Then put the following configuration somewhere on the glue path. The object mapper is from Jackson. It usually comes with Spring.

public class ParameterTypes implements TypeRegistryConfigurer {

    @Override
    public Locale locale() {
        return ENGLISH;
    }

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {
        Transformer transformer = new Transformer();
        typeRegistry.setDefaultDataTableCellTransformer(transformer);
        typeRegistry.setDefaultDataTableEntryTransformer(transformer);
        typeRegistry.setDefaultParameterTransformer(transformer);
    }

    private class Transformer implements ParameterByTypeTransformer, TableEntryByTypeTransformer, TableCellByTypeTransformer {
        ObjectMapper objectMapper = new ObjectMapper();

        @Override
        public Object transform(String s, Type type) {
            return objectMapper.convertValue(s, objectMapper.constructType(type));
        }

        @Override
        public <T> T transform(Map<String, String> map, Class<T> aClass, TableCellByTypeTransformer tableCellByTypeTransformer) {
            return objectMapper.convertValue(map, aClass);
        }

        @Override
        public <T> T transform(String s, Class<T> aClass) {
            return objectMapper.convertValue(s, aClass);
        }
    }
}

Then replace @Getter and @Setter with@Data so hashcode, equals and toString are all implemented.

@Data
public class Employee {

  private Integer id;
  private String name;
  private String department;

}

Then modify your step to use a list of employees instead of a data table. The object mapper installed in the previous step will handle the transform from data table to objects.

@Then("^following list of employees are returned$")
public void following_list_of_employees_are_returned(List<Employee> expectedEmployees) throws Throwable {    
  List<Map<String, Object>> actualEmployees = new ArrayList<>();
  List<Employee> employees = response.as(Employee[].class);
  assertEquals(expectedEmployees, actualEmployees);
}

To make the comparison order insensitive consider using AssertJs assertThat instead of JUnits assertEquals - it usually comes with Spring

Solution 2

Add datatable-matchers to your dependencies

<groupId>io.cucumber</groupId>
<artifactId>datatable-matchers</artifactId>

Create your own data table and compare it using the DataTableHasTheSameRowsAs matcher.

@Then("^following list of employees are returned$")
public void following_list_of_employees_are_returned(DataTable expectedEmployees) {
    List<Employee> employees = response.as(Employee[].class);

    DataTable actualEmployees = createTable(
            employees,
            asList("id", "name", "department"),
            Employee::getId, Employee::getName, Employee::getDepartment
    );
    assertThat(actualEmployees, hasTheSameRowsAs(expectedEmployees));
}


static <T> DataTable createTable(List<T> values, List<String> headers, Function<T, Object>... extractors) {
    List<List<String>> rawTable = new ArrayList<>();
    rawTable.add(headers);
    values.stream()
            .map(employee -> Stream.of(extractors)
                    .map(f -> f.apply(employee))
                    .map(String::valueOf)
                    .collect(Collectors.toList()))
            .forEach(rawTable::add);
    return create(rawTable);
}
like image 163
M.P. Korstanje Avatar answered Sep 19 '22 06:09

M.P. Korstanje