Stringifiers
Stringifiers define how specific types should be converted to strings for comparison in BCT assertions. They provide flexible, customizable string representations that can be tailored to your testing needs.
Built-in Stringifiers
BCT comes with comprehensive built-in stringifiers for common Java types:
// Array types
char[] chars = {'H', 'e', 'l', 'l', 'o'};
assertString("Hello", chars); // Uses charArrayStringifier()
byte[] bytes = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"
assertString("48656C6C6F", bytes); // Uses byteArrayStringifier()
// Date and time types
Date date = new Date(1673780400000L);
assertString("2023-01-15T11:00:00Z", date); // Uses dateStringifier()
GregorianCalendar calendar = new GregorianCalendar(2023, 0, 15);
assertMatchesGlob("2023-01-15T*", calendar); // Uses calendarStringifier()
// Collection types
List<String> list = List.of("a", "b", "c");
assertString("[a,b,c]", list); // Uses listStringifier()
Map<String, String> map = Map.of("key", "value");
assertString("{key=value}", map); // Uses mapStringifier()
// Reflection types
assertString("String", String.class); // Uses classStringifier()
assertString("toString()", Object.class.getMethod("toString")); // Uses methodStringifier()
// Enum types
assertString("RUNNABLE", Thread.State.RUNNABLE); // Uses enumStringifier()
// Stream types
InputStream input = new ByteArrayInputStream("Hello".getBytes());
assertString("48656C6C6F", input); // Uses inputStreamStringifier()
Available Built-in Stringifiers
charArrayStringifier()- Converts char arrays to stringsbyteArrayStringifier()- Converts byte arrays to hex stringsdateStringifier()- Converts Date objects to ISO-8601 formatcalendarStringifier()- Converts Calendar objects to ISO-8601 formatlistStringifier()- Converts List objects to bracket formatmapStringifier()- Converts Map objects to brace formatclassStringifier()- Converts Class objects to readable namesmethodStringifier()- Converts Method objects to signaturesenumStringifier()- Converts Enum values to namesinputStreamStringifier()- Converts InputStream content to hex stringsreaderStringifier()- Converts Reader content to stringsfileStringifier()- Converts File content to strings
Custom Stringifiers
Define custom stringifiers for your domain-specific types:
Basic Custom Stringifier
// Date formatting
Stringifier<LocalDateTime> dateStringifier = (conv, dt) ->
dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// Complex object formatting
Stringifier<Order> orderStringifier = (conv, order) ->
"Order#" + order.getId() + "[" + order.getStatus() + "]";
// Registration
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(LocalDateTime.class, dateStringifier)
.addStringifier(Order.class, orderStringifier)
.build();
Advanced Stringifier Example
// Money type formatting
Stringifier<Money> moneyStringifier = (conv, money) -> {
if (money == null) return "null";
return money.getCurrency().getSymbol() +
money.getAmount().setScale(2, RoundingMode.HALF_UP);
};
// User object with privacy handling
Stringifier<User> userStringifier = (conv, user) -> {
if (user == null) return "null";
// Mask sensitive information
String email = user.getEmail();
String maskedEmail = email.replaceAll("(.{3}).*(@.*)", "$1***$2");
return user.getName() + " <" + maskedEmail + ">";
};
// Registration
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(Money.class, moneyStringifier)
.addStringifier(User.class, userStringifier)
.build();
// Usage in tests
assertBean(args().setBeanConverter(converter),
order, "total,customer", "$99.99,{John Smith <joh***@example.com>}");
Recursive Stringifier
// Complex nested object stringifier
Stringifier<TreeNode> treeStringifier = new Stringifier<TreeNode>() {
@Override
public String stringify(BeanConverter conv, TreeNode node) {
if (node == null) return "null";
StringBuilder sb = new StringBuilder();
sb.append(node.getValue());
if (!node.getChildren().isEmpty()) {
sb.append("[");
for (int i = 0; i < node.getChildren().size(); i++) {
if (i > 0) sb.append(",");
sb.append(stringify(conv, node.getChildren().get(i)));
}
sb.append("]");
}
return sb.toString();
}
};
// Creates output like: "root[child1[grandchild1,grandchild2],child2]"
Using Custom Converters
// Create converter with custom formatting
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(LocalDate.class, date ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.addStringifier(Money.class, money ->
money.getAmount().toPlainString())
.build();
// Use in assertions
assertBean(args().setBeanConverter(converter),
order, "date,total", "2023-12-01,99.99");
Best Practices
When to Create Custom Stringifiers
- Domain objects with complex string representations
- Types requiring specific formatting (dates, currencies, etc.)
- Privacy-sensitive objects that need masking
- Legacy objects without proper toString() implementations
Stringifier Guidelines
- Keep stringifiers simple and focused on string conversion
- Handle null values explicitly
- Avoid side effects or state modifications
- Consider thread safety for shared stringifiers
- Use clear, consistent formatting that aids test readability
Performance Considerations
- Stringifiers are called frequently during testing
- Avoid expensive operations (database calls, file I/O)
- Cache computed values when appropriate
- Consider using lazy evaluation for complex conversions
See Also
- Listifiers - Converting collection-like objects to lists
- 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.