0
# Data Handling
1
2
Robust data table and doc string handling with custom transformations and built-in collection support. Cucumber-Java provides comprehensive support for handling complex test data through data tables and doc strings with automatic conversion to Java collections and custom objects.
3
4
## Capabilities
5
6
### Data Tables
7
8
Access Gherkin data tables as various Java collection types with automatic type conversion and custom transformations.
9
10
```java { .api }
11
/**
12
* Raw DataTable access for maximum flexibility
13
*/
14
public void stepWithDataTable(DataTable table) {
15
List<List<String>> rows = table.asLists();
16
List<Map<String, String>> maps = table.asMaps();
17
}
18
19
/**
20
* List of lists - each row as a list of strings
21
*/
22
public void stepWithListOfLists(List<List<String>> table) { }
23
24
/**
25
* List of maps - each row as a map with headers as keys
26
*/
27
public void stepWithListOfMaps(List<Map<String, String>> table) { }
28
29
/**
30
* Single map - first column as keys, second column as values
31
*/
32
public void stepWithMap(Map<String, String> table) { }
33
34
/**
35
* Map of lists - first column as keys, remaining columns as list values
36
*/
37
public void stepWithMapOfLists(Map<String, List<String>> table) { }
38
39
/**
40
* Map of maps - first column as keys, headers and values as nested maps
41
*/
42
public void stepWithMapOfMaps(Map<String, Map<String, String>> table) { }
43
```
44
45
**Usage Examples:**
46
47
```java
48
import io.cucumber.datatable.DataTable;
49
import io.cucumber.java.en.Given;
50
51
public class DataTableExamples {
52
53
// Raw DataTable for custom processing
54
@Given("the following data:")
55
public void raw_data_table(DataTable table) {
56
List<List<String>> rows = table.asLists();
57
58
// Process headers
59
List<String> headers = rows.get(0);
60
61
// Process data rows
62
for (int i = 1; i < rows.size(); i++) {
63
List<String> row = rows.get(i);
64
// Process each row
65
}
66
67
// Alternative: get as maps
68
List<Map<String, String>> maps = table.asMaps(String.class, String.class);
69
for (Map<String, String> rowMap : maps) {
70
String name = rowMap.get("name");
71
String email = rowMap.get("email");
72
}
73
}
74
75
// List of lists - simple table structure
76
@Given("the matrix:")
77
public void matrix_data(List<List<String>> matrix) {
78
for (List<String> row : matrix) {
79
for (String cell : row) {
80
// Process each cell
81
}
82
}
83
}
84
85
// List of maps - table with headers
86
@Given("the following users exist:")
87
public void users_exist(List<Map<String, String>> users) {
88
for (Map<String, String> user : users) {
89
String name = user.get("name");
90
String email = user.get("email");
91
int age = Integer.parseInt(user.get("age"));
92
93
// Create user object
94
createUser(name, email, age);
95
}
96
}
97
98
// Single map - key-value pairs
99
@Given("the configuration:")
100
public void configuration(Map<String, String> config) {
101
String environment = config.get("environment");
102
String database = config.get("database");
103
String timeout = config.get("timeout");
104
105
// Apply configuration
106
applyConfig(environment, database, timeout);
107
}
108
109
// Map of lists - grouped data
110
@Given("the grouped data:")
111
public void grouped_data(Map<String, List<String>> groups) {
112
List<String> adminUsers = groups.get("admins");
113
List<String> regularUsers = groups.get("users");
114
List<String> guests = groups.get("guests");
115
116
// Process each group
117
}
118
119
// Map of maps - hierarchical data
120
@Given("the user profiles:")
121
public void user_profiles(Map<String, Map<String, String>> profiles) {
122
Map<String, String> johnProfile = profiles.get("john");
123
String johnEmail = johnProfile.get("email");
124
String johnRole = johnProfile.get("role");
125
126
Map<String, String> janeProfile = profiles.get("jane");
127
String janeEmail = janeProfile.get("email");
128
String janeRole = janeProfile.get("role");
129
}
130
}
131
```
132
133
### Data Table Type Conversion
134
135
Automatic conversion of data tables to strongly-typed collections using built-in and custom type converters.
136
137
```java { .api }
138
/**
139
* Built-in numeric type conversions
140
*/
141
public void stepWithIntegers(List<List<Integer>> intTable) { }
142
public void stepWithDoubles(List<List<Double>> doubleTable) { }
143
public void stepWithFloats(List<List<Float>> floatTable) { }
144
public void stepWithLongs(List<List<Long>> longTable) { }
145
public void stepWithBigDecimals(List<List<BigDecimal>> decimalTable) { }
146
public void stepWithBigIntegers(List<List<BigInteger>> bigIntTable) { }
147
148
/**
149
* Custom object conversion using @DataTableType
150
*/
151
public void stepWithCustomObjects(List<CustomObject> objects) { }
152
```
153
154
**Usage Examples:**
155
156
```java
157
import java.math.BigDecimal;
158
import java.math.BigInteger;
159
160
public class TypedDataTableExamples {
161
162
// Integer data table
163
@Given("the following numbers:")
164
public void numbers(List<List<Integer>> numberTable) {
165
for (List<Integer> row : numberTable) {
166
for (Integer number : row) {
167
// Process integer values directly
168
processNumber(number);
169
}
170
}
171
}
172
173
// Double precision data
174
@Given("the coordinates:")
175
public void coordinates(List<Map<String, Double>> coords) {
176
for (Map<String, Double> coord : coords) {
177
Double x = coord.get("x");
178
Double y = coord.get("y");
179
Double z = coord.get("z");
180
181
// Work with double values directly
182
plotPoint(x, y, z);
183
}
184
}
185
186
// Financial data with BigDecimal
187
@Given("the following prices:")
188
public void prices(List<Map<String, BigDecimal>> priceData) {
189
for (Map<String, BigDecimal> item : priceData) {
190
String product = item.get("product").toString();
191
BigDecimal price = item.get("price");
192
BigDecimal tax = item.get("tax");
193
194
// Precise decimal calculations
195
BigDecimal total = price.add(tax);
196
}
197
}
198
199
// Custom object conversion (requires @DataTableType)
200
@Given("the following products:")
201
public void products(List<Product> products) {
202
for (Product product : products) {
203
// Work with strongly-typed Product objects
204
saveProduct(product);
205
}
206
}
207
208
// Mixed type map
209
@Given("the test data:")
210
public void test_data(Map<String, Object> data) {
211
// Note: requires @DefaultDataTableCellTransformer for Object conversion
212
String name = (String) data.get("name");
213
Integer count = (Integer) data.get("count");
214
Boolean active = (Boolean) data.get("active");
215
}
216
}
217
```
218
219
### Doc Strings
220
221
Access Gherkin doc strings as raw strings or convert to custom objects using doc string type transformers.
222
223
```java { .api }
224
/**
225
* Raw doc string access
226
*/
227
public void stepWithDocString(String docString) { }
228
229
/**
230
* DocString object with metadata
231
*/
232
public void stepWithDocStringObject(DocString docString) {
233
String content = docString.getContent();
234
String contentType = docString.getContentType();
235
}
236
237
/**
238
* Custom object conversion using @DocStringType
239
*/
240
public void stepWithCustomDocString(CustomObject parsedDocString) { }
241
```
242
243
**Usage Examples:**
244
245
```java
246
import io.cucumber.docstring.DocString;
247
import io.cucumber.java.en.Given;
248
249
public class DocStringExamples {
250
251
// Raw string doc string
252
@Given("the following JSON:")
253
public void json_data(String jsonString) {
254
// Parse JSON manually
255
ObjectMapper mapper = new ObjectMapper();
256
try {
257
JsonNode json = mapper.readTree(jsonString);
258
// Process JSON
259
} catch (Exception e) {
260
throw new RuntimeException("Invalid JSON", e);
261
}
262
}
263
264
// DocString object with metadata
265
@Given("the following document:")
266
public void document_data(DocString docString) {
267
String content = docString.getContent();
268
String contentType = docString.getContentType(); // From """ content_type
269
270
// Process based on content type
271
if ("json".equals(contentType)) {
272
processJsonContent(content);
273
} else if ("xml".equals(contentType)) {
274
processXmlContent(content);
275
} else {
276
processPlainText(content);
277
}
278
}
279
280
// Multi-line text processing
281
@Given("the following email template:")
282
public void email_template(String template) {
283
// Process multi-line email template
284
String[] lines = template.split("\n");
285
String subject = extractSubject(lines);
286
String body = extractBody(lines);
287
288
sendEmail(subject, body);
289
}
290
291
// Large text data
292
@Given("the following log entries:")
293
public void log_entries(String logData) {
294
String[] entries = logData.split("\n");
295
for (String entry : entries) {
296
if (!entry.trim().isEmpty()) {
297
parseLogEntry(entry);
298
}
299
}
300
}
301
302
// Custom parsed doc string (requires @DocStringType)
303
@Given("the following configuration:")
304
public void configuration(Properties config) {
305
// Automatically parsed from doc string to Properties object
306
String environment = config.getProperty("environment");
307
String database = config.getProperty("database.url");
308
309
applyConfiguration(config);
310
}
311
312
// Structured data from doc string
313
@Given("the API response:")
314
public void api_response(JsonNode response) {
315
// Automatically parsed from JSON doc string
316
String status = response.get("status").asText();
317
JsonNode data = response.get("data");
318
319
validateResponse(status, data);
320
}
321
}
322
```
323
324
### Advanced Data Table Features
325
326
Complex data table operations including transposition, custom transformations, and nested structures.
327
328
```java { .api }
329
/**
330
* Transposed data tables
331
*/
332
public void stepWithTransposedTable(@Transpose DataTable table) { }
333
public void stepWithTransposedObjects(@Transpose List<CustomObject> objects) { }
334
335
/**
336
* Nested data structures
337
*/
338
public void stepWithNestedMaps(Map<String, Map<String, Object>> nestedData) { }
339
public void stepWithNestedLists(List<List<List<String>>> nestedLists) { }
340
341
/**
342
* Custom replacement handling
343
*/
344
public void stepWithReplacements(List<CustomObject> objects) { }
345
// Uses @DataTableType(replaceWithEmptyString = {"[blank]", "null"})
346
```
347
348
**Usage Examples:**
349
350
```java
351
import io.cucumber.java.Transpose;
352
import io.cucumber.datatable.DataTable;
353
354
public class AdvancedDataTableExamples {
355
356
// Transposed table for vertical data layout
357
@Given("the user profile:")
358
public void user_profile(@Transpose DataTable profile) {
359
// Input table: | name | John Doe |
360
// | email | john@test.com |
361
// | age | 30 |
362
//
363
// After transpose: | name | email | age |
364
// | John Doe | john@test.com | 30 |
365
366
List<Map<String, String>> profileMaps = profile.asMaps();
367
Map<String, String> userInfo = profileMaps.get(0);
368
369
String name = userInfo.get("name");
370
String email = userInfo.get("email");
371
int age = Integer.parseInt(userInfo.get("age"));
372
}
373
374
// Complex nested structure
375
@Given("the department structure:")
376
public void department_structure(Map<String, Map<String, List<String>>> deptStructure) {
377
// Table with department -> role -> list of people
378
Map<String, List<String>> engineering = deptStructure.get("Engineering");
379
List<String> developers = engineering.get("Developer");
380
List<String> managers = engineering.get("Manager");
381
382
assignPeopleToDepartment("Engineering", "Developer", developers);
383
assignPeopleToDepartment("Engineering", "Manager", managers);
384
}
385
386
// Custom object with empty value handling
387
@Given("the product catalog:")
388
public void product_catalog(List<Product> products) {
389
// Uses @DataTableType with replaceWithEmptyString configuration
390
for (Product product : products) {
391
// Empty/null values automatically handled by transformer
392
if (product.getDescription() != null) {
393
processDescription(product.getDescription());
394
}
395
}
396
}
397
398
// Multi-dimensional data
399
@Given("the test matrix:")
400
public void test_matrix(List<List<TestCase>> testMatrix) {
401
for (int i = 0; i < testMatrix.size(); i++) {
402
List<TestCase> row = testMatrix.get(i);
403
for (int j = 0; j < row.size(); j++) {
404
TestCase testCase = row.get(j);
405
runTest(i, j, testCase);
406
}
407
}
408
}
409
410
// Dynamic column handling
411
@Given("the dynamic data:")
412
public void dynamic_data(DataTable table) {
413
List<String> headers = table.row(0);
414
415
for (int i = 1; i < table.height(); i++) {
416
Map<String, String> rowData = new HashMap<>();
417
List<String> row = table.row(i);
418
419
for (int j = 0; j < headers.size(); j++) {
420
String header = headers.get(j);
421
String value = j < row.size() ? row.get(j) : "";
422
rowData.put(header, value);
423
}
424
425
processDynamicRow(rowData);
426
}
427
}
428
}
429
```
430
431
### Error Handling and Validation
432
433
Robust error handling and validation for data table and doc string processing.
434
435
```java
436
public class DataValidationExamples {
437
438
@Given("the validated user data:")
439
public void validated_users(List<Map<String, String>> users) {
440
for (Map<String, String> user : users) {
441
// Validate required fields
442
validateRequired(user, "name", "email");
443
444
// Validate data formats
445
String email = user.get("email");
446
if (!isValidEmail(email)) {
447
throw new IllegalArgumentException("Invalid email: " + email);
448
}
449
450
// Validate numeric fields
451
String ageStr = user.get("age");
452
if (ageStr != null && !ageStr.isEmpty()) {
453
try {
454
int age = Integer.parseInt(ageStr);
455
if (age < 0 || age > 150) {
456
throw new IllegalArgumentException("Invalid age: " + age);
457
}
458
} catch (NumberFormatException e) {
459
throw new IllegalArgumentException("Age must be a number: " + ageStr);
460
}
461
}
462
}
463
}
464
465
@Given("the JSON configuration:")
466
public void json_config(String jsonString) {
467
try {
468
ObjectMapper mapper = new ObjectMapper();
469
JsonNode config = mapper.readTree(jsonString);
470
471
// Validate required configuration fields
472
if (!config.has("environment")) {
473
throw new IllegalArgumentException("Missing required field: environment");
474
}
475
476
// Process validated configuration
477
processConfiguration(config);
478
479
} catch (JsonProcessingException e) {
480
throw new IllegalArgumentException("Invalid JSON configuration: " + e.getMessage());
481
}
482
}
483
484
private void validateRequired(Map<String, String> data, String... fields) {
485
for (String field : fields) {
486
String value = data.get(field);
487
if (value == null || value.trim().isEmpty()) {
488
throw new IllegalArgumentException("Required field missing or empty: " + field);
489
}
490
}
491
}
492
493
private boolean isValidEmail(String email) {
494
return email != null && email.contains("@") && email.contains(".");
495
}
496
}
497
```