Как раз задавался вопросом, каким образом надежно и универсально парсить таблицу в DTO. Вцелом я вижу 2 способа решать подобную задачу:
-
(ортодоксальный) Определять параметр definition метода как DataTable
, либо как Map<,>
(List<Map<,>>
для коллекции) и конвертировать в индивидуальном порядке.
-
(молодежный, Cucumber 3+) Конвертировать таблицу в POJO автоматически через TypeRegistryConfigurer.
В варианте №1 все понятно - с помощью оператора new
, геттеров/сеттеров мы парсим таблицу/мапу в объект. Можно даже прикрутить какой-то BeanUtils
(apache или spring) чтобы сократить бойлерплейт. Такой способ работает если классов несколько, иначе плохо масштабируется.
В варианте №2 нужно использовать что-то, что умеет конвертировать мапу в объект, например Jackson
. Теперь осталось только определить, как jackson’ом делать конвертацию String → Map.Entry.Value → поле DTO для случая коллекций. К сожалению, я не нашел чисто jackson’овского решения, поэтому String → Map.Entry.Value придется делать своими силами.
Итак, решение следующее:
- Закидываем класс
ParameterTypes
из ссылки выше куда-то в glue package.
- Решаем, каким образом мы будем писать коллекции в ячейке таблицы в feature файле (чтобы потом правильно её парсить). Пускай формат будет типа
[val1, val2]
(квадратные скобки, разделитель запятая и 0-1 пробелов)
- в
ParameterTypes
меняем имплементацию метода
public <T> T transform(Map<String, String> map, Class<T> aClass, TableCellByTypeTransformer tableCellByTypeTransformer)
примерно на такую:
@Override
public <T> T transform(Map<String, String> map, Class<T> aClass, TableCellByTypeTransformer tableCellByTypeTransformer) {
// обходим мапу, чтобы сконвертировать ячейки
Map<String, Object> converted = map.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> {
var value = e.getValue();
if (StringUtils.startsWith(value, "[") && StringUtils.endsWith(value, "]")) {
String collectionStr = StringUtils.strip(value, "[]");
return StringUtils.split(collectionStr, ", ");
}
return value;
}));
try {
String json = objectMapper.writeValueAsString(converted);
return objectMapper.readerFor(aClass).readValue(json);
} catch (IOException ex) {
throw new IllegalArgumentException("Failed to convert map to DTO", ex);
}
}
- Вот и все. Осталось протестить:
@lombok.Data
public class User {
private String firstName;
private String lastName;
private String workEmail;
private String workPhone;
private String userName;
private Set<String> assignedRoles;
private Set<String> assignedAdvisorCodes;
}
Scenario: print users
Given I have next users:
| firstName | lastName | workEmail | workPhone | userName | assignedRoles | assignedAdvisorCodes |
| Steven | Gerrard | steeveg@test.com | 12312312346345 | steeveg | [Advisor, Compliance] | [1107,1108] |
@Given("I have next users:")
public void printUsers(List<User> users) {
log.info("Users received: {}", users);
}
[INFO ] 11:40:38: Users received: [User(firstName=Steven, lastName=Gerrard, workEmail=steeveg@test.com, workPhone=12312312346345, userName=steeveg, assignedRoles=[Compliance, Advisor], assignedAdvisorCodes=[1107, 1108])]
Примечания:
- Если в таблице всегда 1 строка, то в параметре definition метода вместо
List<User>
можно оставить просто User
- будет работать и так.
- Иногда есть необходимость передавать через таблицу сложные DTO. Например, у вас есть такое:
@Data
public class Organization {
private User manager;
private List<User> employees;
}
Решение следующее - записывать такое DTO как плоский JSON. Все что нужно сделать - подключить в проект библиотеку, например, json-flattener, и при конвертации таблицы вместо этого:
String json = objectMapper.writeValueAsString(converted);
Использовать это:
String json = JsonUnflattener.unflatten(objectMapper.writeValueAsString(converted));
Тогда в таблице мы можем записать наш DTO как плоский JSON:
Scenario: print organization
Given I have next organization:
| manager.firstName | manager.lastName | employees[0].userName | employees[1].userName |
| Steve | Jobs | Bobby | Dobby |
@Given("I have next organization:")
public void printOrganization(Organization organization) {
log.info("Organization received: {}", organization);
}
[INFO ] 11:50:53: Organization received: Organization(manager=User(firstName=Steve, lastName=Jobs, workEmail=null, workPhone=null, userName=null, assignedRoles=null, assignedAdvisorCodes=null), employees=[User(firstName=null, lastName=null, workEmail=null, workPhone=null, userName=Bobby, assignedRoles=null, assignedAdvisorCodes=null), User(firstName=null, lastName=null, workEmail=null, workPhone=null, userName=Dobby, assignedRoles=null, assignedAdvisorCodes=null)])