Skip to main content

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​

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.junit.bct.annotations.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 sorting
  • TriState.FALSE - Disable map sorting
  • TriState.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 sorting
  • TriState.FALSE - Disable collection sorting
  • TriState.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.junit.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, date ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
// Add custom stringifier for Money
.addStringifier(Money.class, 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, date ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build();
BctConfiguration.set(converter);
}

@AfterEach
void tearDown() {
BctConfiguration.clear(); // Also clears converter
}

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 RuntimeException is 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​

Discussion

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