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.
You can compare data table with another data in tabular format. This data can be from excel, database or even from the REST services.
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.
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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With