Skip to main content

Listifiers

Listifiers convert collection-like objects into lists for use in BCT assertions. They provide a unified way to work with various collection types, iterators, streams, and other iterable data structures.

Built-in Listifiers

BCT comes with comprehensive built-in listifiers for collection-like Java types:

// Collection types
List<String> list = List.of("a", "b", "c");
assertList(list, "a", "b", "c"); // Uses collectionListifier()

Set<Integer> set = Set.of(1, 2, 3);
assertList(set, "1", "2", "3"); // Uses iterableListifier()

// Iterator types
Iterator<String> iterator = List.of("x", "y", "z").iterator();
assertList(iterator, "x", "y", "z"); // Uses iteratorListifier()

// Stream types
Stream<String> stream = Stream.of("p", "q", "r");
assertList(stream, "p", "q", "r"); // Uses streamListifier()

// Enumeration types
Vector<String> vector = new Vector<>(List.of("m", "n", "o"));
Enumeration<String> enumeration = vector.elements();
assertList(enumeration, "m", "n", "o"); // Uses enumerationListifier()

// Map types
Map<String, String> map = Map.of("key1", "value1", "key2", "value2");
assertList(map, "key1=value1", "key2=value2"); // Uses mapListifier()

Available Built-in Listifiers

  • collectionListifier() - Converts Collection objects to List&lt;Object&gt;
  • iterableListifier() - Converts Iterable objects to List&lt;Object&gt;
  • iteratorListifier() - Converts Iterator objects to List&lt;Object&gt; (consumes the iterator)
  • streamListifier() - Converts Stream objects to List&lt;Object&gt; (terminates the stream)
  • enumerationListifier() - Converts Enumeration objects to List&lt;Object&gt; (consumes the enumeration)
  • mapListifier() - Converts Map objects to List&lt;Map.Entry&gt; objects

Custom Listifiers

Define custom listifiers for domain-specific collection types:

Basic Custom Listifier

// Custom collection type
Listifier<CustomCollection> customListifier = (conv, collection) -> {
if (collection == null) return null;
List<Object> result = new ArrayList<>();
collection.forEach(result::add);
return result;
};

// Registration
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addListifier(CustomCollection.class, customListifier)
.build();

Advanced Listifier Example

// Paginated result listifier
Listifier<PaginatedResult> paginatedListifier = (conv, page) -> {
if (page == null) return null;
// Extract items from paginated wrapper
return new ArrayList<>(page.getItems());
};

// Database result set listifier
Listifier<ResultSet> resultSetListifier = (conv, rs) -> {
if (rs == null) return null;
List<Object> rows = new ArrayList<>();
try {
int columnCount = rs.getMetaData().getColumnCount();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(rs.getMetaData().getColumnName(i), rs.getObject(i));
}
rows.add(row);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return rows;
};

// Registration
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addListifier(PaginatedResult.class, paginatedListifier)
.addListifier(ResultSet.class, resultSetListifier)
.build();

Tree Structure Listifier

// Convert tree to flat list (breadth-first)
Listifier<TreeNode> treeBreadthFirstListifier = (conv, root) -> {
if (root == null) return null;
List<Object> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);

while (!queue.isEmpty()) {
TreeNode node = queue.poll();
result.add(node.getValue());
queue.addAll(node.getChildren());
}

return result;
};

// Convert tree to flat list (depth-first)
Listifier<TreeNode> treeDepthFirstListifier = (conv, root) -> {
if (root == null) return null;
List<Object> result = new ArrayList<>();
collectDepthFirst(root, result);
return result;
};

private void collectDepthFirst(TreeNode node, List<Object> result) {
if (node != null) {
result.add(node.getValue());
node.getChildren().forEach(child -> collectDepthFirst(child, result));
}
}

Usage Examples

Testing Custom Collections

// Test paginated results
PaginatedResult<User> page = userService.getUsers(pageNumber);
assertList(args().setBeanConverter(converter),
page, "Alice", "Bob", "Charlie");

// Test database results
ResultSet rs = statement.executeQuery("SELECT name FROM users");
assertList(args().setBeanConverter(converter),
rs,
predicate(row -> ((Map)row).get("name").equals("Alice")),
predicate(row -> ((Map)row).get("name").equals("Bob")));

Combining with assertBean

// Test collection properties
assertBean(args().setBeanConverter(converter),
paginatedResult,
"items{#{name}},totalCount",
"[{Alice},{Bob},{Charlie}],3");

Important Notes

Consuming vs Non-Consuming Listifiers

Some listifiers consume their input:

// Iterator is consumed - can only be used once
Iterator<String> iterator = list.iterator();
assertList(iterator, "a", "b", "c");
// iterator is now exhausted and cannot be reused

// Stream is terminated - can only be used once
Stream<String> stream = Stream.of("a", "b", "c");
assertList(stream, "a", "b", "c");
// stream is now closed and cannot be reused

// Enumeration is consumed - can only be used once
Enumeration<String> enumeration = vector.elements();
assertList(enumeration, "a", "b", "c");
// enumeration is now exhausted

Thread Safety

// Ensure thread-safe listifiers for concurrent testing
Listifier<ThreadSafeCollection> threadSafeListifier = (conv, collection) -> {
if (collection == null) return null;
synchronized (collection) {
return new ArrayList<>(collection.getItems());
}
};

Best Practices

When to Create Custom Listifiers

  • Custom collection implementations
  • Wrapper objects containing collections
  • Database result sets or cursors
  • Paginated results
  • Tree or graph structures that need flattening
  • Legacy collection types

Listifier Guidelines

  • Always handle null input appropriately
  • Return null for null input (not empty list)
  • Create defensive copies when necessary
  • Consider whether the listifier consumes its input
  • Document consumption behavior clearly
  • Ensure thread safety for shared listifiers

Performance Considerations

  • Avoid copying large collections unnecessarily
  • Use streaming approaches for large datasets
  • Consider lazy evaluation for expensive conversions
  • Be aware of memory implications for large lists

Error Handling

// Robust listifier with error handling
Listifier<DataSource> safeListifier = (conv, source) -> {
if (source == null) return null;
try {
return source.fetchAll();
} catch (Exception e) {
// Return list with error indication
return List.of("Error: " + e.getMessage());
}
};

See Also

Discussion

Share feedback or follow-up questions for this page directly through GitHub.