Customization
BCT provides flexible customization options through the @BctConfig annotation and the BctConfiguration class. These tools allow you to configure assertion behavior, customize property access, and extend BCT's capabilities to match your testing needs.
Table of Contents
- @BctConfig Annotation
- BctConfiguration Class
- Configuration Properties
- Custom Bean Converters
- Thread Safety
- Advanced Customization Topics
@BctConfig Annotation
The @BctConfig annotation provides a declarative way to configure BCT settings for your tests. It can be applied at both the class level and method level, with method-level annotations taking precedence over class-level annotations.
Basic Usage
import org.apache.juneau.bct.BctConfig;
import org.apache.juneau.commons.lang.TriState;
@BctConfig(sortMaps=TriState.TRUE)
class MyTest {
@Test
@BctConfig(sortCollections=TriState.TRUE)
void testSomething() {
// sortMaps=true (from class), sortCollections=true (from method)
assertBean(order, "items{0{name}}", "{{Laptop}}");
}
}
Annotation Properties
sortMaps
Controls whether map entries are sorted by key before comparison in assertions.
@BctConfig(sortMaps=TriState.TRUE)
class MyTest {
@Test
void testMap() {
// Map entries will be sorted before comparison
Map<String, String> map = Map.of("zebra", "animal", "apple", "fruit");
assertBean(map, "apple,zebra", "fruit,animal");
}
}
Values:
TriState.TRUE- Enable map sortingTriState.FALSE- Disable map sortingTriState.UNSET- Inherit from class-level annotation (default for method-level)
sortCollections
Controls whether collection elements are sorted before comparison in assertions.
@BctConfig(sortCollections=TriState.TRUE)
class MyTest {
@Test
void testList() {
// List elements will be sorted before comparison
List<String> list = Arrays.asList("zebra", "apple", "banana");
assertList(list, "apple", "banana", "zebra");
}
}
Values:
TriState.TRUE- Enable collection sortingTriState.FALSE- Disable collection sortingTriState.UNSET- Inherit from class-level annotation (default for method-level)
beanConverter
Specifies a custom bean converter class to use for all assertions in the test.
public static class MyCustomConverter extends BasicBeanConverter {
public MyCustomConverter() {
super(BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(LocalDate.class, date ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build());
}
}
@BctConfig(beanConverter=MyCustomConverter.class)
class MyTest {
@Test
void testWithCustomConverter() {
// All assertions use MyCustomConverter
assertBean(event, "date", "2023-12-01");
}
}
Requirements:
- The converter class must have a no-arg constructor
- The constructor will be made accessible if needed (for nested classes, etc.)
- If instantiation fails, the test will fail with a descriptive error message
Class-Level vs Method-Level
Method-level annotations override class-level annotations for the same property. Properties not specified in the method annotation inherit from the class annotation.
@BctConfig(sortMaps=TriState.TRUE, beanConverter=MyConverter.class)
class MyTest {
@Test
@BctConfig(sortMaps=TriState.FALSE, sortCollections=TriState.TRUE)
void testMethod() {
// sortMaps=false (from method, overrides class)
// sortCollections=true (from method)
// beanConverter=MyConverter (from class, inherited)
}
@Test
void testAnotherMethod() {
// sortMaps=true (from class)
// sortCollections=UNSET (default, not set)
// beanConverter=MyConverter (from class)
}
}
Using Default Converter
To explicitly use the default converter when a class-level converter is set, use BasicBeanConverter.class:
@BctConfig(beanConverter=MyConverter.class)
class MyTest {
@Test
@BctConfig(beanConverter=BasicBeanConverter.class)
void testWithDefaultConverter() {
// Uses default converter, not MyConverter
}
}
BctConfiguration Class
The BctConfiguration class provides a programmatic API for configuring BCT settings. It's useful when you need dynamic configuration or want to set up converters in test setup methods.
Setting Configuration Values
import static org.apache.juneau.bct.BctConfiguration.*;
@BeforeEach
void setUp() {
// Enable map sorting
set(BCT_SORT_MAPS, true);
// Enable collection sorting
set(BCT_SORT_COLLECTIONS, true);
// Set custom converter
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(LocalDate.class, date ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build();
set(converter);
}
@AfterEach
void tearDown() {
// Clear all thread-local settings
clear();
}
Getting Configuration Values
// Get a setting with a default value
boolean sortMaps = BctConfiguration.get(BCT_SORT_MAPS, false);
// Get a setting object (can check if set)
StringSetting setting = BctConfiguration.get(BCT_SORT_MAPS);
if (setting != null && setting.asBoolean()) {
// Map sorting is enabled
}
Global vs Thread-Local Settings
Settings can be set globally (affecting all threads) or thread-locally (affecting only the current thread). Thread-local settings take precedence.
// Set global setting (affects all threads)
BctConfiguration.setGlobal(BCT_SORT_MAPS, true);
// Set thread-local setting (overrides global for this thread)
BctConfiguration.set(BCT_SORT_MAPS, false);
// Clear thread-local settings
BctConfiguration.clear();
// Clear global settings
BctConfiguration.clearGlobal();
Configuration Properties
BCT_SORT_MAPS
Property name: "Bct.sortMaps"
When enabled, map entries are sorted by key before comparison, ensuring consistent assertion results regardless of map implementation or insertion order.
BctConfiguration.set(BctConfiguration.BCT_SORT_MAPS, true);
BCT_SORT_COLLECTIONS
Property name: "Bct.sortCollections"
When enabled, collection elements are sorted before comparison, ensuring consistent assertion results regardless of collection implementation or insertion order.
BctConfiguration.set(BctConfiguration.BCT_SORT_COLLECTIONS, true);
Custom Bean Converters
Bean converters control how objects are accessed and converted to strings for comparison. BCT uses BasicBeanConverter.DEFAULT by default, but you can provide custom converters for specialized needs.
Creating a Custom Converter
public static class MyCustomConverter extends BasicBeanConverter {
public MyCustomConverter() {
super(BasicBeanConverter.builder()
.defaultSettings()
// Add custom stringifier for LocalDate
.addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
// Add custom stringifier for Money
.addStringifier(Money.class, (conv,money) ->
money.getAmount().toPlainString())
.build());
}
}
Using via Annotation
@BctConfig(beanConverter=MyCustomConverter.class)
class MyTest {
@Test
void testWithCustomConverter() {
assertBean(order, "date,total", "2023-12-01,99.99");
}
}
Using via Programmatic API
@BeforeEach
void setUp() {
var converter = BasicBeanConverter.builder()
.defaultSettings()
.addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build();
setConverter(converter); // BctAssertions.setConverter(...) - a thin passthrough to BctConfiguration.set(...)
}
@AfterEach
void tearDown() {
resetConverter(); // BctAssertions.resetConverter() - a thin passthrough to BctConfiguration.clear()
}
Converter Requirements
- Must have a no-arg constructor
- Constructor will be made accessible if needed (for nested classes, private constructors, etc.)
- If instantiation fails, a
RuntimeExceptionis thrown with a descriptive message
Thread Safety
All configuration in BCT is thread-safe and uses thread-local storage. This ensures that:
- Parallel test execution doesn't interfere with each other's settings
- Each test thread has its own isolated configuration
- Settings are automatically cleared after each test when using
@BctConfig
Thread-Local Storage
// Thread 1
BctConfiguration.set(BCT_SORT_MAPS, true);
// Thread 2 (running in parallel)
BctConfiguration.set(BCT_SORT_MAPS, false);
// Each thread has its own setting - no interference
Automatic Cleanup
When using @BctConfig, settings are automatically cleared after each test method:
@BctConfig(sortMaps=TriState.TRUE)
class MyTest {
@Test
void test1() {
// sortMaps is enabled
}
@Test
void test2() {
// sortMaps is still enabled (from class annotation)
// But if test1 had method-level annotation, it would be cleared
}
}
Advanced Customization Topics
For more advanced customization options, see:
- Stringifiers - Define how objects are converted to strings for comparison
- Listifiers - Convert collection-like objects to lists for testing
- Swappers - Transform objects before processing (unwrap Optional, Supplier, etc.)
See Also
- Bean-Centric Testing Basics - Main BCT documentation
- Custom Error Messages - Enhanced error reporting
- Property Extractors - Custom property access logic
In This Section
📄️ 2.6.2.2.1. 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.
📄️ 2.6.2.2.2. 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.
📄️ 2.6.2.2.3. Swappers
Swappers transform objects before they are processed by BCT assertions. They provide a way to unwrap, evaluate, or transform objects into more testable forms. Swappers are particularly useful for wrapper types like Optional, Supplier, and Future.