List of Java Objects to Cucumber DataTable to perform diff

Arun B Chandrasekaran :

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.

mpkorstanje :

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);
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=94818&siteId=1