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 toList<Object>iterableListifier()- Converts Iterable objects toList<Object>iteratorListifier()- Converts Iterator objects toList<Object>(consumes the iterator)streamListifier()- Converts Stream objects toList<Object>(terminates the stream)enumerationListifier()- Converts Enumeration objects toList<Object>(consumes the enumeration)mapListifier()- Converts Map objects toList<Map.Entry>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
- Stringifiers - Converting objects to strings
- Swappers - Transforming objects before processing
- PropertyExtractors - Custom property access logic
- juneau-bct Basics - Main BCT documentation
Share feedback or follow-up questions for this page directly through GitHub.