This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 94175423d6 juneau-bct improvements
94175423d6 is described below
commit 94175423d67b94361672faf5f81053abb2cadcdd
Author: James Bognar <[email protected]>
AuthorDate: Fri Dec 19 12:25:17 2025 -0500
juneau-bct improvements
---
.../org/apache/juneau/bean/openapi3/MediaType.java | 4 +-
.../org/apache/juneau/bean/openapi3/Parameter.java | 2 +-
.../org/apache/juneau/bean/openapi3/PathItem.java | 2 +-
.../apache/juneau/bean/openapi3/SchemaInfo.java | 4 +-
.../org/apache/juneau/bean/swagger/HeaderInfo.java | 6 +-
.../java/org/apache/juneau/bean/swagger/Items.java | 6 +-
.../apache/juneau/bean/swagger/ParameterInfo.java | 4 +-
.../org/apache/juneau/bean/swagger/SchemaInfo.java | 6 +-
juneau-core/juneau-bct/pom.xml | 3 +-
.../org/apache/juneau/junit/bct/BctAssertions.java | 197 ++++--------
.../apache/juneau/junit/bct/BctConfiguration.java | 309 ++++++++++++++++++
.../org/apache/juneau/junit/bct/Listifiers.java | 6 +-
.../juneau/junit/bct/annotations/BctConfig.java | 90 ++++++
.../junit/bct/annotations/BctConfigExtension.java | 76 +++++
.../org/apache/juneau/commons/lang/TriState.java | 77 +++++
.../docs/topics/07.01.00.JuneauBctBasics.md | 48 ++-
.../docs/topics/07.01.04.PropertyExtractors.md | 7 +-
...Messages.md => 07.02.00.CustomErrorMessages.md} | 9 +-
juneau-docs/docs/topics/07.03.00.Customization.md | 351 +++++++++++++++++++++
...01.Stringifiers.md => 07.03.01.Stringifiers.md} | 7 +-
....01.02.Listifiers.md => 07.03.02.Listifiers.md} | 7 +-
.../{07.01.03.Swappers.md => 07.03.03.Swappers.md} | 7 +-
juneau-docs/sidebars.ts | 42 ++-
.../java/org/apache/juneau/BeanSession_Test.java | 2 -
.../juneau/bean/openapi3/HeaderInfo_Test.java | 2 +-
.../apache/juneau/bean/openapi3/Items_Test.java | 2 +-
.../juneau/bean/openapi3/Parameter_Test.java | 2 +-
.../juneau/bean/openapi3/SchemaInfo_Test.java | 2 +-
.../juneau/bean/swagger/SchemaInfo_Test.java | 2 +-
.../juneau/commons/collections/Maps_Test.java | 3 +
.../juneau/commons/collections/Sets_Test.java | 3 +
.../juneau/junit/bct/BasicBeanConverter_Test.java | 4 +
.../juneau/junit/bct/BctAssertions_Test.java | 3 +
.../apache/juneau/junit/bct/BctConfig_Test.java | 306 ++++++++++++++++++
.../apache/juneau/junit/bct/Listifiers_Test.java | 9 +-
35 files changed, 1398 insertions(+), 212 deletions(-)
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/MediaType.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/MediaType.java
index 603524e5a7..e5b8d6c18a 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/MediaType.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/MediaType.java
@@ -182,10 +182,10 @@ public class MediaType extends OpenApiElement {
public Set<String> keySet() {
// @formatter:off
var s = setb(String.class)
- .addIf(nn(schema), "schema")
- .addIf(nn(example), "x-example")
.addIf(isNotEmpty(encoding), "encoding")
.addIf(isNotEmpty(examples), "examples")
+ .addIf(nn(schema), "schema")
+ .addIf(nn(example), "x-example")
.build();
// @formatter:on
return new MultiSet<>(s, super.keySet());
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
index e19989ae90..f932b39284 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
@@ -234,8 +234,8 @@ public class Parameter extends OpenApiElement {
var s = setb(String.class)
.addIf(nn(allowEmptyValue), "allowEmptyValue")
.addIf(nn(allowReserved), "allowReserved")
- .addIf(nn(description), "description")
.addIf(nn(deprecated), "deprecated")
+ .addIf(nn(description), "description")
.addIf(nn(example), "example")
.addIf(nn(examples), "examples")
.addIf(nn(explode), "explode")
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
index 2cbefe48a3..c6f97e0465 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
@@ -235,8 +235,8 @@ public class PathItem extends OpenApiElement {
.addIf(nn(get), "get")
.addIf(nn(head), "head")
.addIf(nn(options), "options")
- .addIf(nn(patch), "patch")
.addIf(nn(parameters), "parameters")
+ .addIf(nn(patch), "patch")
.addIf(nn(post), "post")
.addIf(nn(put), "put")
.addIf(nn(servers), "servers")
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
index 0d8348eb66..fc87313b4f 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
@@ -654,14 +654,14 @@ public class SchemaInfo extends OpenApiElement {
.addIf(nn(externalDocs), "externalDocs")
.addIf(nn(format), "format")
.addIf(nn(items), "items")
- .addIf(nn(maximum), "maximum")
.addIf(nn(maxItems), "maxItems")
.addIf(nn(maxLength), "maxLength")
.addIf(nn(maxProperties), "maxProperties")
- .addIf(nn(minimum), "minimum")
+ .addIf(nn(maximum), "maximum")
.addIf(nn(minItems), "minItems")
.addIf(nn(minLength), "minLength")
.addIf(nn(minProperties), "minProperties")
+ .addIf(nn(minimum), "minimum")
.addIf(nn(multipleOf), "multipleOf")
.addIf(nn(not), "not")
.addIf(nn(nullable), "nullable")
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/HeaderInfo.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/HeaderInfo.java
index 9d9ea2a833..dfc7af071e 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/HeaderInfo.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/HeaderInfo.java
@@ -351,6 +351,7 @@ public class HeaderInfo extends SwaggerElement {
public Set<String> keySet() {
// @formatter:off
var s = setb(String.class)
+ .addIf(nn(ref), "$ref")
.addIf(nn(collectionFormat), "collectionFormat")
.addIf(nn(default_), "default")
.addIf(nn(description), "description")
@@ -360,15 +361,14 @@ public class HeaderInfo extends SwaggerElement {
.addIf(nn(exclusiveMinimum), "exclusiveMinimum")
.addIf(nn(format), "format")
.addIf(nn(items), "items")
- .addIf(nn(maximum), "maximum")
.addIf(nn(maxItems), "maxItems")
.addIf(nn(maxLength), "maxLength")
- .addIf(nn(minimum), "minimum")
+ .addIf(nn(maximum), "maximum")
.addIf(nn(minItems), "minItems")
.addIf(nn(minLength), "minLength")
+ .addIf(nn(minimum), "minimum")
.addIf(nn(multipleOf), "multipleOf")
.addIf(nn(pattern), "pattern")
- .addIf(nn(ref), "$ref")
.addIf(nn(type), "type")
.addIf(nn(uniqueItems), "uniqueItems")
.build();
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Items.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Items.java
index 0fba5a81e3..44e909c794 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Items.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Items.java
@@ -328,6 +328,7 @@ public class Items extends SwaggerElement {
public Set<String> keySet() {
// @formatter:off
var s = setb(String.class)
+ .addIf(nn(ref), "$ref")
.addIf(nn(collectionFormat), "collectionFormat")
.addIf(nn(default_), "default")
.addIf(isNotEmpty(enum_), "enum")
@@ -335,15 +336,14 @@ public class Items extends SwaggerElement {
.addIf(nn(exclusiveMinimum), "exclusiveMinimum")
.addIf(nn(format), "format")
.addIf(nn(items), "items")
- .addIf(nn(maximum), "maximum")
.addIf(nn(maxItems), "maxItems")
.addIf(nn(maxLength), "maxLength")
- .addIf(nn(minimum), "minimum")
+ .addIf(nn(maximum), "maximum")
.addIf(nn(minItems), "minItems")
.addIf(nn(minLength), "minLength")
+ .addIf(nn(minimum), "minimum")
.addIf(nn(multipleOf), "multipleOf")
.addIf(nn(pattern), "pattern")
- .addIf(nn(ref), "$ref")
.addIf(nn(type), "type")
.addIf(nn(uniqueItems), "uniqueItems")
.build();
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ParameterInfo.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ParameterInfo.java
index f622b6ef6e..f00dbaa152 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ParameterInfo.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ParameterInfo.java
@@ -550,12 +550,12 @@ public class ParameterInfo extends SwaggerElement {
.addIf(nn(format), "format")
.addIf(nn(in), "in")
.addIf(nn(items), "items")
- .addIf(nn(maximum), "maximum")
.addIf(nn(maxItems), "maxItems")
.addIf(nn(maxLength), "maxLength")
- .addIf(nn(minimum), "minimum")
+ .addIf(nn(maximum), "maximum")
.addIf(nn(minItems), "minItems")
.addIf(nn(minLength), "minLength")
+ .addIf(nn(minimum), "minimum")
.addIf(nn(multipleOf), "multipleOf")
.addIf(nn(name), "name")
.addIf(nn(pattern), "pattern")
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SchemaInfo.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SchemaInfo.java
index 3450de04ca..4c5d244271 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SchemaInfo.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SchemaInfo.java
@@ -557,6 +557,7 @@ public class SchemaInfo extends SwaggerElement {
public Set<String> keySet() {
// @formatter:off
var s = setb(String.class)
+ .addIf(nn(ref), "$ref")
.addIf(nn(additionalProperties), "additionalProperties")
.addIf(isNotEmpty(allOf), "allOf")
.addIf(nn(default_), "default")
@@ -569,19 +570,18 @@ public class SchemaInfo extends SwaggerElement {
.addIf(nn(externalDocs), "externalDocs")
.addIf(nn(format), "format")
.addIf(nn(items), "items")
- .addIf(nn(maximum), "maximum")
.addIf(nn(maxItems), "maxItems")
.addIf(nn(maxLength), "maxLength")
.addIf(nn(maxProperties), "maxProperties")
- .addIf(nn(minimum), "minimum")
+ .addIf(nn(maximum), "maximum")
.addIf(nn(minItems), "minItems")
.addIf(nn(minLength), "minLength")
.addIf(nn(minProperties), "minProperties")
+ .addIf(nn(minimum), "minimum")
.addIf(nn(multipleOf), "multipleOf")
.addIf(nn(pattern), "pattern")
.addIf(isNotEmpty(properties), "properties")
.addIf(nn(readOnly), "readOnly")
- .addIf(nn(ref), "$ref")
.addIf(nn(required), "required")
.addIf(isNotEmpty(requiredProperties),
"requiredProperties")
.addIf(nn(title), "title")
diff --git a/juneau-core/juneau-bct/pom.xml b/juneau-core/juneau-bct/pom.xml
index 460f27b870..25f1489736 100644
--- a/juneau-core/juneau-bct/pom.xml
+++ b/juneau-core/juneau-bct/pom.xml
@@ -43,7 +43,8 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
- <version>6.0.1</version>
+ <version>${junit.version}</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
index 28707af99d..2189b3210f 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
@@ -27,7 +27,6 @@ import java.util.*;
import java.util.function.*;
import java.util.stream.*;
-import org.apache.juneau.commons.function.*;
import org.apache.juneau.commons.utils.*;
import org.opentest4j.*;
@@ -118,53 +117,53 @@ import org.opentest4j.*;
* <js>"prop1,prop2"</js>, <js>"value1,value2"</js>);
* </p>
*
- * <h5 class='section'>Customizing the Default Converter:</h5>
- * <p>The default bean converter can be customized on a per-thread
basis using:</p>
- * <ul>
- * <li><b>{@link #setConverter(BeanConverter)}:</b> Set a custom
converter for the current thread</li>
- * <li><b>{@link #resetConverter()}:</b> Reset to the system default
converter</li>
- * </ul>
- *
- * <p class='bjava'>
- * <jc>// Example: Set custom converter in @BeforeEach method</jc>
- * <ja>@BeforeEach</ja>
- * <jk>void</jk> <jsm>setUp</jsm>() {
- * <jk>var</jk> <jv>customConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
- * .defaultSettings()
- * .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> ->
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
- * .addStringifier(MyType.<jk>class</jk>, <jp>obj</jp> ->
<jp>obj</jp>.customFormat())
- * .build();
- *
BctAssertions.<jsm>setConverter</jsm>(<jv>customConverter</jv>);
- * }
- *
- * <jc>// All assertions in this test class now use the custom
converter</jc>
- * <ja>@Test</ja>
- * <jk>void</jk> <jsm>testWithCustomConverter</jsm>() {
- * <jsm>assertBean</jsm>(<jv>myObject</jv>,
<js>"date,property"</js>, <js>"2023-12-01,value"</js>);
- * }
- *
- * <jc>// Clean up in @AfterEach method</jc>
- * <ja>@AfterEach</ja>
- * <jk>void</jk> <jsm>tearDown</jsm>() {
- * BctAssertions.<jsm>resetConverter</jsm>();
- * }
- * </p>
- *
- * <p class='bjava'>
- * <jc>// Example: Per-test method converter override</jc>
- * <ja>@Test</ja>
- * <jk>void</jk> <jsm>testSpecificFormat</jsm>() {
- * <jk>var</jk> <jv>dateConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
- * .defaultSettings()
- * .addStringifier(LocalDateTime.<jk>class</jk>, <jp>dt</jp>
-> <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_DATE_TIME</jsf>))
- * .build();
- * BctAssertions.<jsm>setConverter</jsm>(<jv>dateConverter</jv>);
- * <jkt>try</jkt> {
- * <jsm>assertBean</jsm>(<jv>event</jv>, <js>"timestamp"</js>,
<js>"2023-12-01T10:30:00"</js>);
- * } <jkt>finally</jkt> {
- * BctAssertions.<jsm>resetConverter</jsm>();
- * }
- * }
+ * <h5 class='section'>Customizing the Default Converter:</h5>
+ * <p>The default bean converter can be customized on a per-thread basis
using:</p>
+ * <ul>
+ * <li><b>{@link #setConverter(BeanConverter)}:</b> Set a custom converter
for the current thread</li>
+ * <li><b>{@link #resetConverter()}:</b> Reset to the system default
converter</li>
+ * </ul>
+ *
+ * <p class='bjava'>
+ * <jc>// Example: Set custom converter in @BeforeEach method</jc>
+ * <ja>@BeforeEach</ja>
+ * <jk>void</jk> <jsm>setUp</jsm>() {
+ * <jk>var</jk> <jv>customConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
+ * .defaultSettings()
+ * .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> ->
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
+ * .addStringifier(MyType.<jk>class</jk>, <jp>obj</jp> ->
<jp>obj</jp>.customFormat())
+ * .build();
+ * BctAssertions.<jsm>setConverter</jsm>(<jv>customConverter</jv>);
+ * }
+ *
+ * <jc>// All assertions in this test class now use the custom
converter</jc>
+ * <ja>@Test</ja>
+ * <jk>void</jk> <jsm>testWithCustomConverter</jsm>() {
+ * <jsm>assertBean</jsm>(<jv>myObject</jv>, <js>"date,property"</js>,
<js>"2023-12-01,value"</js>);
+ * }
+ *
+ * <jc>// Clean up in @AfterEach method</jc>
+ * <ja>@AfterEach</ja>
+ * <jk>void</jk> <jsm>tearDown</jsm>() {
+ * BctAssertions.<jsm>resetConverter</jsm>();
+ * }
+ * </p>
+ *
+ * <p class='bjava'>
+ * <jc>// Example: Per-test method converter override</jc>
+ * <ja>@Test</ja>
+ * <jk>void</jk> <jsm>testSpecificFormat</jsm>() {
+ * <jk>var</jk> <jv>dateConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
+ * .defaultSettings()
+ * .addStringifier(LocalDateTime.<jk>class</jk>, <jp>dt</jp> ->
<jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_DATE_TIME</jsf>))
+ * .build();
+ * BctAssertions.<jsm>setConverter</jsm>(<jv>dateConverter</jv>);
+ * <jkt>try</jkt> {
+ * <jsm>assertBean</jsm>(<jv>event</jv>, <js>"timestamp"</js>,
<js>"2023-12-01T10:30:00"</js>);
+ * } <jkt>finally</jkt> {
+ * BctAssertions.<jsm>resetConverter</jsm>();
+ * }
+ * }
* </p>
*
* <h5 class='section'>Performance and Thread Safety:</h5>
@@ -183,12 +182,6 @@ import org.opentest4j.*;
*/
public class BctAssertions {
- // Thread-local memoized supplier for default converter (defaults to
BasicBeanConverter.DEFAULT)
- private static final ThreadLocal<ResettableSupplier<BeanConverter>>
CONVERTER_SUPPLIER = ThreadLocal.withInitial(() -> memoizeResettable(() ->
BasicBeanConverter.DEFAULT));
-
- // Thread-local override for method-level converter customization
- private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE =
new ThreadLocal<>();
-
/**
* Asserts that the fields/properties on the specified bean are the
specified values after being converted to strings.
*
@@ -391,7 +384,7 @@ public class BctAssertions {
assertNotNull(actual, "Actual was null.");
assertArgNotNull("fields", fields);
assertArgNotNull("expected", expected);
- var converter = getConverter();
+ var converter = BctConfiguration.getConverter();
assertEquals(expected, tokenize(fields).stream().map(x ->
converter.getNested(actual, x)).collect(joining(",")), composeMessage(message,
"Bean assertion failed."));
}
@@ -473,7 +466,7 @@ public class BctAssertions {
assertArgNotNull("fields", fields);
assertArgNotNull("expected", expected);
- var converter = getConverter();
+ var converter = BctConfiguration.getConverter();
var tokens = tokenize(fields);
var errors = new ArrayList<AssertionFailedError>();
List<Object> actualList = converter.listify(actual);
@@ -549,7 +542,7 @@ public class BctAssertions {
assertArgNotNull("actual", actual);
assertNotNull(actual, "Value was null.");
- var a = getConverter().stringify(actual);
+ var a = BctConfiguration.getConverter().stringify(actual);
assertTrue(a.contains(expected), composeMessage(message,
"String did not contain expected substring. ==> expected: <{0}> but was:
<{1}>", expected, a));
}
@@ -596,7 +589,7 @@ public class BctAssertions {
assertArgNotNull("expected", expected);
assertNotNull(actual, "Value was null.");
- var a = getConverter().stringify(actual);
+ var a = BctConfiguration.getConverter().stringify(actual);
var errors = new ArrayList<AssertionFailedError>();
for (var e : expected) {
@@ -683,7 +676,7 @@ public class BctAssertions {
*/
public static void assertEmpty(Supplier<String> message, Object value) {
assertNotNull(value, "Value was null.");
- var size = getConverter().size(value);
+ var size = BctConfiguration.getConverter().size(value);
assertEquals(0, size, composeMessage(message, "Value was not
empty. Size=<{0}>", size));
}
@@ -761,7 +754,7 @@ public class BctAssertions {
assertArgNotNull("expected", expected);
assertArgNotNull("actual", actual);
- var converter = getConverter();
+ var converter = BctConfiguration.getConverter();
List<Object> list = converter.listify(actual);
var errors = new ArrayList<AssertionFailedError>();
@@ -990,7 +983,7 @@ public class BctAssertions {
assertArgNotNull("pattern", pattern);
assertNotNull(value, "Value was null.");
- var v = getConverter().stringify(value);
+ var v = BctConfiguration.getConverter().stringify(value);
var m = StringUtils.getGlobMatchPattern(pattern).matcher(v);
assertTrue(m.matches(), composeMessage(message, "Pattern
didn''t match. ==> pattern: <{0}> but was: <{1}>", pattern, v));
}
@@ -1056,7 +1049,7 @@ public class BctAssertions {
*/
public static void assertNotEmpty(Supplier<String> message, Object
value) {
assertNotNull(value, "Value was null.");
- int size = getConverter().size(value);
+ int size = BctConfiguration.getConverter().size(value);
assertTrue(size > 0, composeMessage(message, "Value was
empty."));
}
@@ -1102,7 +1095,7 @@ public class BctAssertions {
*/
public static void assertSize(Supplier<String> message, int expected,
Object actual) {
assertNotNull(actual, "Value was null.");
- var size = getConverter().size(actual);
+ var size = BctConfiguration.getConverter().size(actual);
assertEquals(expected, size, composeMessage(message, "Value not
expected size."));
}
@@ -1153,67 +1146,7 @@ public class BctAssertions {
assertNotNull(actual, "Value was null.");
var messageSupplier = message != null ? message : fs("");
- assertEquals(expected, getConverter().stringify(actual),
messageSupplier);
- }
-
- /**
- * Resets the bean converter for the current thread to the system
default.
- *
- * <p>This method clears any thread-local converter override set via
{@link #setConverter(BeanConverter)},
- * restoring the default converter ({@link BasicBeanConverter#DEFAULT})
for subsequent assertions.</p>
- *
- * <p>This is typically called in test teardown methods (e.g., {@code
@AfterEach}) to clean up after tests
- * that set a custom converter.</p>
- *
- * <h5 class='section'>Usage Example:</h5>
- * <p class='bjava'>
- * <jc>// In @AfterEach method</jc>
- * BctAssertions.<jsm>resetConverter</jsm>();
- * </p>
- *
- * <h5 class='section'>Thread Safety:</h5>
- * <p>This method is thread-safe and only affects the current thread's
converter.</p>
- *
- * @see #setConverter(BeanConverter)
- */
- public static void resetConverter() {
- CONVERTER_OVERRIDE.remove();
- CONVERTER_SUPPLIER.get().reset();
- }
-
- /**
- * Sets a custom bean converter for the current thread.
- *
- * <p>This method allows you to override the default converter for all
assertions in the current test method.
- * The converter will be used by all assertion methods in the current
thread.</p>
- *
- * <p>This is particularly useful in test setup methods (e.g., {@code
@BeforeEach}) to configure a custom converter
- * for all tests in a test class or method.</p>
- *
- * <h5 class='section'>Usage Example:</h5>
- * <p class='bjava'>
- * <jc>// In @BeforeEach method</jc>
- * <jk>var</jk> <jv>customConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
- * .defaultSettings()
- * .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> ->
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
- * .build();
- * BctAssertions.<jsm>setConverter</jsm>(<jv>customConverter</jv>);
- *
- * <jc>// All subsequent assertions in this test method will use the
custom converter</jc>
- * <jsm>assertBean</jsm>(<jv>event</jv>, <js>"date"</js>,
<js>"2023-12-01"</js>);
- * </p>
- *
- * <h5 class='section'>Thread Safety:</h5>
- * <p>This method is thread-safe and uses thread-local storage. Each
test method running in parallel
- * will have its own converter instance, preventing cross-thread
interference.</p>
- *
- * @param converter The bean converter to use for the current thread.
Must not be <jk>null</jk>.
- * @throws IllegalArgumentException If converter is <jk>null</jk>.
- * @see #resetConverter()
- */
- public static void setConverter(BeanConverter converter) {
- assertArgNotNull("converter", converter);
- CONVERTER_OVERRIDE.set(converter);
+ assertEquals(expected,
BctConfiguration.getConverter().stringify(actual), messageSupplier);
}
/**
@@ -1234,21 +1167,5 @@ public class BctAssertions {
return fs("{0}, Caused by: {1}", customMessage.get(),
f(defaultMessage, defaultArgs));
}
- /**
- * Gets the bean converter for the current thread.
- *
- * <p>Returns the thread-local converter override if set, otherwise
returns the memoized default converter.
- * This method is used internally by all assertion methods to get the
current thread-local converter.</p>
- *
- * @return The bean converter to use for the current thread.
- */
- private static BeanConverter getConverter() {
- var override = CONVERTER_OVERRIDE.get();
- if (override != null) {
- return override;
- }
- return CONVERTER_SUPPLIER.get().get();
- }
-
private BctAssertions() {}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
new file mode 100644
index 0000000000..47dc749466
--- /dev/null
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctConfiguration.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.junit.bct;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import org.apache.juneau.commons.function.*;
+import org.apache.juneau.commons.settings.*;
+
+/**
+ * Configuration utility for Bean-Centric Testing (BCT) framework.
+ *
+ * <p>
+ * This class provides thread-local configuration settings for BCT assertions,
allowing test-specific
+ * customization of behavior such as map sorting, collection sorting, and bean
converter selection.
+ *
+ * <p>
+ * Configuration is managed using thread-local storage, ensuring that parallel
test execution doesn't
+ * interfere with each other's settings. Settings are typically configured via
the {@link org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig}
+ * annotation, but can also be set programmatically.
+ *
+ * <h5 class='section'>Configuration Properties:</h5>
+ * <ul>
+ * <li><b>{@link #BCT_SORT_MAPS}:</b> Enable sorting of maps in
assertions</li>
+ * <li><b>{@link #BCT_SORT_COLLECTIONS}:</b> Enable sorting of collections
in assertions</li>
+ * <li><b>Bean Converter:</b> Custom bean converter instance for property
access and conversion</li>
+ * </ul>
+ *
+ * <h5 class='section'>Usage via Annotation:</h5>
+ * <p class='bjava'>
+ * <ja>@BctConfig</ja>(sortMaps=TriState.<jsf>TRUE</jsf>,
beanConverter=MyConverter.<jk>class</jk>)
+ * <jk>class</jk> MyTest {
+ * <ja>@Test</ja>
+ * <ja>@BctConfig</ja>(sortCollections=TriState.<jsf>TRUE</jsf>)
+ * <jk>void</jk> testSomething() {
+ * <jc>// sortMaps=true (from class), sortCollections=true
(from method),</jc>
+ * <jc>// beanConverter=MyConverter (from class)</jc>
+ * }
+ * }
+ * </p>
+ *
+ * <h5 class='section'>Usage via Programmatic API:</h5>
+ * <p class='bjava'>
+ * <ja>@BeforeEach</ja>
+ * <jk>void</jk> setUp() {
+ *
BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>,
<jk>true</jk>);
+ * BctConfiguration.<jsm>set</jsm>(<jk>new</jk>
MyCustomConverter());
+ * }
+ *
+ * <ja>@AfterEach</ja>
+ * <jk>void</jk> tearDown() {
+ * BctConfiguration.<jsm>clear</jsm>();
+ * }
+ * </p>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * All methods in this class are thread-safe. Configuration is stored in
thread-local storage,
+ * ensuring that each test thread has its own isolated configuration that
doesn't interfere
+ * with other concurrently running tests.
+ * </p>
+ *
+ * @see org.apache.juneau.junit.bct.annotations.BctConfig
+ * @see org.apache.juneau.junit.bct.BctAssertions
+ */
+public class BctConfiguration {
+
+ // Thread-local memoized supplier for default converter (defaults to
BasicBeanConverter.DEFAULT)
+ private static final ThreadLocal<ResettableSupplier<BeanConverter>>
CONVERTER_SUPPLIER = ThreadLocal.withInitial(() -> memoizeResettable(() ->
BasicBeanConverter.DEFAULT));
+
+ /**
+ * Configuration property name for enabling map sorting in BCT
assertions.
+ *
+ * <p>
+ * When set to <jk>true</jk>, maps will be sorted by key before
comparison in assertions.
+ * This ensures consistent ordering regardless of the map's internal
ordering.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *
BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>,
<jk>true</jk>);
+ * </p>
+ */
+ public static final String BCT_SORT_MAPS = "Bct.sortMaps";
+
+ /**
+ * Configuration property name for enabling collection sorting in BCT
assertions.
+ *
+ * <p>
+ * When set to <jk>true</jk>, collections will be sorted before
comparison in assertions.
+ * This ensures consistent ordering regardless of the collection's
internal ordering.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *
BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_COLLECTIONS</jsf>,
<jk>true</jk>);
+ * </p>
+ */
+ public static final String BCT_SORT_COLLECTIONS = "Bct.sortCollections";
+
+ // Thread-local override for method-level converter customization
+ private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE =
new ThreadLocal<>();
+
+ private static final Settings SETTINGS = Settings.create().build();
+
+ /**
+ * Sets a thread-local configuration value.
+ *
+ * <p>
+ * The value is stored in thread-local storage and will only affect the
current thread.
+ * This is the recommended way to set configuration for individual
tests.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *
BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>,
<jk>true</jk>);
+ * </p>
+ *
+ * @param name The configuration property name (e.g., {@link
#BCT_SORT_MAPS}).
+ * @param value The value to set.
+ * @see #get(String)
+ * @see #clear()
+ */
+ public static void set(String name, Object value) {
+ SETTINGS.setLocal(name, s(value));
+ }
+
+ /**
+ * Sets a global configuration value.
+ *
+ * <p>
+ * Global values apply to all threads and persist until explicitly
cleared.
+ * Use with caution in multi-threaded test environments.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *
BctConfiguration.<jsm>setGlobal</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>,
<jk>true</jk>);
+ * </p>
+ *
+ * @param name The configuration property name (e.g., {@link
#BCT_SORT_MAPS}).
+ * @param value The value to set.
+ * @see #get(String)
+ * @see #clearGlobal()
+ */
+ public static void setGlobal(String name, Object value) {
+ SETTINGS.setGlobal(name, s(value));
+ }
+
+ /**
+ * Gets a configuration setting by name.
+ *
+ * <p>
+ * Returns the setting object which can be used to check if a value is
set and retrieve it.
+ * Thread-local values take precedence over global values.
+ *
+ * @param name The configuration property name (e.g., {@link
#BCT_SORT_MAPS}).
+ * @return The setting object, or <jk>null</jk> if not set.
+ * @see #get(String, Object)
+ * @see #set(String, Object)
+ */
+ public static StringSetting get(String name) {
+ return SETTINGS.get(name);
+ }
+
+ /**
+ * Gets a configuration value by name with a default value.
+ *
+ * <p>
+ * Returns the configured value if set, otherwise returns the provided
default value.
+ * Thread-local values take precedence over global values.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>sortMaps</jv> =
BctConfiguration.<jsm>get</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>,
<jk>false</jk>);
+ * </p>
+ *
+ * @param <T> The type of the value.
+ * @param name The configuration property name (e.g., {@link
#BCT_SORT_MAPS}).
+ * @param def The default value to return if not set.
+ * @return The configured value, or the default if not set.
+ * @see #get(String)
+ * @see #set(String, Object)
+ */
+ public static <T> T get(String name, T def) {
+ return SETTINGS.get(name, def);
+ }
+
+ /**
+ * Clears all thread-local configuration settings.
+ *
+ * <p>
+ * This method removes all thread-local settings including:
+ * <ul>
+ * <li>Configuration properties (e.g., {@link #BCT_SORT_MAPS},
{@link #BCT_SORT_COLLECTIONS})</li>
+ * <li>Bean converter override</li>
+ * </ul>
+ *
+ * <p>
+ * This is typically called in test teardown methods (e.g., {@code
@AfterEach}) to ensure
+ * a clean state for subsequent tests. The {@link
org.apache.juneau.junit.bct.annotations.BctConfigExtension BctConfigExtension}
+ * automatically calls this method after each test.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <ja>@AfterEach</ja>
+ * <jk>void</jk> tearDown() {
+ * BctConfiguration.<jsm>clear</jsm>();
+ * }
+ * </p>
+ *
+ * @see #clearGlobal()
+ * @see #set(String, Object)
+ */
+ public static void clear() {
+ SETTINGS.clearLocal();
+ CONVERTER_OVERRIDE.remove();
+ }
+
+ /**
+ * Clears all global configuration settings.
+ *
+ * <p>
+ * This method removes all global settings. Use with caution as this
affects all threads.
+ *
+ * @see #clear()
+ * @see #setGlobal(String, Object)
+ */
+ public static void clearGlobal() {
+ SETTINGS.clearGlobal();
+ }
+
+ /**
+ * Sets a custom bean converter for the current thread.
+ *
+ * <p>
+ * This method allows you to override the default converter ({@link
BasicBeanConverter#DEFAULT}) for all
+ * assertions in the current test method. The converter will be used by
all assertion methods in the current thread.
+ *
+ * <p>
+ * This is particularly useful when you need custom property access,
stringification, or conversion logic
+ * for specific tests. The converter can be configured via the {@link
org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig}
+ * annotation or set programmatically.
+ *
+ * <h5 class='section'>Usage Example:</h5>
+ * <p class='bjava'>
+ * <ja>@BeforeEach</ja>
+ * <jk>void</jk> setUp() {
+ * <jk>var</jk> <jv>customConverter</jv> =
BasicBeanConverter.<jsm>builder</jsm>()
+ * .defaultSettings()
+ * .addStringifier(LocalDate.<jk>class</jk>,
<jp>date</jp> ->
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
+ * .build();
+ *
BctConfiguration.<jsm>set</jsm>(<jv>customConverter</jv>);
+ * }
+ *
+ * <ja>@Test</ja>
+ * <jk>void</jk> testWithCustomConverter() {
+ * <jc>// All assertions use the custom converter</jc>
+ * <jsm>assertBean</jsm>(<jv>event</jv>, <js>"date"</js>,
<js>"2023-12-01"</js>);
+ * }
+ *
+ * <ja>@AfterEach</ja>
+ * <jk>void</jk> tearDown() {
+ * BctConfiguration.<jsm>clear</jsm>(); <jc>// Also clears
converter override</jc>
+ * }
+ * </p>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This method is thread-safe and uses thread-local storage. Each test
method running in parallel
+ * will have its own converter instance, preventing cross-thread
interference.
+ * </p>
+ *
+ * @param converter The bean converter to use for the current thread.
Must not be <jk>null</jk>.
+ * @throws IllegalArgumentException If converter is <jk>null</jk>.
+ * @see #clear()
+ * @see
org.apache.juneau.junit.bct.annotations.BctConfig#beanConverter()
+ * @see BeanConverter
+ * @see BasicBeanConverter
+ */
+ public static void set(BeanConverter converter) {
+ assertArgNotNull("converter", converter);
+ CONVERTER_OVERRIDE.set(converter);
+ }
+
+
+ /**
+ * Gets the bean converter for the current thread.
+ *
+ * <p>Returns the thread-local converter override if set, otherwise
returns the memoized default converter.
+ * This method is used internally by all assertion methods to get the
current thread-local converter.</p>
+ *
+ * @return The bean converter to use for the current thread.
+ */
+ static BeanConverter getConverter() {
+ return
opt(BctConfiguration.CONVERTER_OVERRIDE.get()).orElseGet(CONVERTER_SUPPLIER.get());
+ }
+}
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
index 35bd0045dd..917917dfd9 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
@@ -20,6 +20,7 @@ import static java.util.Collections.*;
import static java.util.Spliterators.*;
import static java.util.stream.StreamSupport.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.junit.bct.BctConfiguration.*;
import java.util.*;
import java.util.stream.*;
@@ -134,7 +135,7 @@ public class Listifiers {
@SuppressWarnings("unchecked")
public static Listifier<Collection> collectionListifier() {
return (bc, collection) -> {
- if (collection instanceof Set && ! (collection
instanceof SortedSet) && ! (collection instanceof LinkedHashSet)) {
+ if (collection instanceof Set && ! (collection
instanceof SortedSet) && ! (collection instanceof LinkedHashSet) &&
BctConfiguration.get(BCT_SORT_COLLECTIONS, false)) {
// TODO - This is too unreliable.
var collection2 = new
TreeSet<>(flexibleComparator(bc));
collection2.addAll(collection);
@@ -314,8 +315,7 @@ public class Listifiers {
@SuppressWarnings("unchecked")
public static Listifier<Map> mapListifier() {
return (bc, map) -> {
- if (! (map instanceof SortedMap) && ! (map instanceof
LinkedHashMap)) {
- // TODO - This is too unreliable.
+ if (! (map instanceof SortedMap) && ! (map instanceof
LinkedHashMap) && BctConfiguration.get(BCT_SORT_MAPS, false)) {
var map2 = new
TreeMap<>(flexibleComparator(bc));
map2.putAll(map);
map = map2;
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfig.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfig.java
new file mode 100644
index 0000000000..e3224c4f4b
--- /dev/null
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfig.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.junit.bct.annotations;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.commons.lang.*;
+import org.apache.juneau.junit.bct.*;
+import org.junit.jupiter.api.extension.*;
+
+/**
+ * Annotation for configuring BCT settings in JUnit 5 tests.
+ *
+ * <p>
+ * This annotation can be applied to test classes or test methods to
automatically configure
+ * BCT settings before tests run and clear them after tests complete.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <ja>@BctConfig</ja>(sortMaps=TriState.<jsf>TRUE</jsf>)
+ * <jk>class</jk> MyTest {
+ * <ja>@Test</ja>
+ * <ja>@BctConfig</ja>(sortCollections=TriState.<jsf>TRUE</jsf>)
+ * <jk>void</jk> testFoo() {
+ * <jc>// sortMaps and sortCollections are both
enabled</jc>
+ * }
+ * }
+ * </p>
+ *
+ * <p>
+ * Method-level annotations override class-level annotations.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(BctConfigExtension.class)
+public @interface BctConfig {
+
+ /**
+ * Enable sorting of maps in BCT assertions.
+ *
+ * @return {@link TriState#TRUE} to enable map sorting, {@link
TriState#FALSE} to disable,
+ * or {@link TriState#UNSET} to inherit from class-level
annotation.
+ */
+ TriState sortMaps() default TriState.UNSET;
+
+ /**
+ * Enable sorting of collections in BCT assertions.
+ *
+ * @return {@link TriState#TRUE} to enable collection sorting, {@link
TriState#FALSE} to disable,
+ * or {@link TriState#UNSET} to inherit from class-level
annotation.
+ */
+ TriState sortCollections() default TriState.UNSET;
+
+ /**
+ * Custom bean converter class to use for BCT assertions.
+ *
+ * <p>
+ * If specified (not the default {@link BeanConverter}), the class will
be instantiated using
+ * its no-arg constructor and set as the converter for the current
thread.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *
<ja>@BctConfiguration</ja>(beanConverter=MyCustomConverter.<jk>class</jk>)
+ * <jk>class</jk> MyTest {
+ * <ja>@Test</ja>
+ * <jk>void</jk> testFoo() {
+ * <jc>// Uses MyCustomConverter for all
assertions</jc>
+ * }
+ * }
+ * </p>
+ *
+ * @return The bean converter class to instantiate and use, or {@link
BeanConverter} to use the default.
+ */
+ Class<? extends BeanConverter> beanConverter() default
BeanConverter.class;
+}
+
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfigExtension.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfigExtension.java
new file mode 100644
index 0000000000..c606f3081b
--- /dev/null
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/annotations/BctConfigExtension.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.junit.bct.annotations;
+
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+import static org.apache.juneau.junit.bct.BctConfiguration.*;
+import static org.apache.juneau.commons.lang.TriState.*;
+
+import java.util.*;
+
+import org.apache.juneau.junit.bct.*;
+import org.junit.jupiter.api.extension.*;
+
+/**
+ * JUnit 5 extension that processes {@link BctConfig} annotations.
+ *
+ * <p>
+ * This extension automatically sets BCT settings before test execution and
clears them after.
+ * It supports both class-level and method-level annotations, with
method-level taking precedence.
+ */
+public class BctConfigExtension implements BeforeEachCallback,
AfterEachCallback {
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ var al = getAnnotations(context);
+
+ if (al.isEmpty())
+ return;
+
+ clear();
+
+ al.stream().map(x -> x.sortMaps()).filter(x -> ne(x,
UNSET)).findFirst().ifPresent(x -> set(BCT_SORT_MAPS, x == TRUE));
+ al.stream().map(x -> x.sortCollections()).filter(x -> ne(x,
UNSET)).findFirst().ifPresent(x -> set(BCT_SORT_COLLECTIONS, x == TRUE));
+ al.stream().map(x -> x.beanConverter()).filter(x -> ne(x,
BeanConverter.class)).findFirst().map(x -> eq(x, BasicBeanConverter.class) ?
null : x).ifPresent(x -> setConverter(x));
+ }
+
+ private static void setConverter(Class<? extends BeanConverter> x) {
+ safe(()->{
+ var c = x.getDeclaredConstructor();
+ c.setAccessible(true);
+ set(c.newInstance());
+ }, e -> rex(e, "Failed to instantiate BeanConverter: {0}. It
must have a no-arg constructor.", x.getName()));
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ var al = getAnnotations(context);
+
+ if (al.isEmpty())
+ return;
+
+ clear();
+ }
+
+ private static List<BctConfig> getAnnotations(ExtensionContext context)
{
+ var l = new ArrayList<BctConfig>();
+ context.getTestMethod().map(x ->
x.getAnnotation(BctConfig.class)).ifPresent(x -> l.add(x));
+ context.getTestClass().map(x ->
x.getAnnotation(BctConfig.class)).ifPresent(x -> l.add(x));
+ return l;
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/TriState.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/TriState.java
new file mode 100644
index 0000000000..6b668f96fb
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/TriState.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.lang;
+
+/**
+ * Three-state enumeration for boolean-like values that can be explicitly set
or unset.
+ *
+ * <p>
+ * This enum is useful in scenarios where you need to distinguish between:
+ * <ul>
+ * <li>A value that is explicitly set to <jk>true</jk></li>
+ * <li>A value that is explicitly set to <jk>false</jk></li>
+ * <li>A value that is not set (should inherit from a parent/default)</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// In an annotation</jc>
+ * <jk>public @interface</jk> MyConfig {
+ * TriState <jsm>enabled</jsm>() <jk>default</jk>
TriState.<jsf>UNSET</jsf>;
+ * }
+ *
+ * <jc>// Usage</jc>
+ * <ja>@MyConfig</ja>(enabled = TriState.<jsf>TRUE</jsf>) <jc>//
Explicitly enabled</jc>
+ * <ja>@MyConfig</ja>(enabled = TriState.<jsf>FALSE</jsf>) <jc>//
Explicitly disabled</jc>
+ * <ja>@MyConfig</ja> <jc>// Not set,
inherits default</jc>
+ * </p>
+ */
+public enum TriState {
+ /** Explicitly set to <jk>true</jk>. */
+ TRUE,
+
+ /** Explicitly set to <jk>false</jk>. */
+ FALSE,
+
+ /** Not set - should inherit from parent or use default value. */
+ UNSET;
+
+ /**
+ * Converts this TriState to a boolean value.
+ *
+ * <p>
+ * If this is {@link #UNSET}, returns the provided default value.
+ *
+ * @param defaultValue The default value to use if this is {@link
#UNSET}.
+ * @return <jk>true</jk> if this is {@link #TRUE}, <jk>false</jk> if
this is {@link #FALSE},
+ * or the provided default if this is {@link #UNSET}.
+ */
+ public boolean toBoolean(boolean defaultValue) {
+ return this == UNSET ? defaultValue : (this == TRUE);
+ }
+
+ /**
+ * Converts a boolean to a TriState.
+ *
+ * @param value The boolean value.
+ * @return {@link #TRUE} if <jk>true</jk>, {@link #FALSE} if
<jk>false</jk>.
+ */
+ public static TriState fromBoolean(boolean value) {
+ return value ? TRUE : FALSE;
+ }
+}
+
diff --git a/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
b/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
index e8187f5ae2..9a593e8408 100644
--- a/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
+++ b/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
@@ -18,11 +18,12 @@ A powerful and intuitive testing framework that extends
JUnit with streamlined a
## 📚 Advanced Topics
-- [Stringifiers](07.01.01.Stringifiers.md) - Custom string conversions
-- [Listifiers](07.01.02.Listifiers.md) - Custom list conversions
-- [Swappers](07.01.03.Swappers.md) - Object transformation
+- [Custom Error Messages](07.02.00.CustomErrorMessages.md) - Enhanced error
reporting
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Stringifiers](07.03.01.Stringifiers.md) - Custom string conversions
+ - [Listifiers](07.03.02.Listifiers.md) - Custom list conversions
+ - [Swappers](07.03.03.Swappers.md) - Object transformation
- [Property Extractors](07.01.04.PropertyExtractors.md) - Custom property
access
-- [Custom Error Messages](07.01.05.CustomErrorMessages.md) - Enhanced error
reporting
## Overview
@@ -536,9 +537,33 @@ assertBean(user, "orders{size,#{total}}",
"{3,[{99.99},{149.99},{79.99}]}");
BCT provides several advanced configuration options for customizing assertion
behavior:
+### @BctConfig Annotation
+
+The `@BctConfig` annotation provides a declarative way to configure BCT
settings for your tests. It can be applied at the class level or method level,
with method-level annotations taking precedence.
+
+```java
+@BctConfig(sortMaps=TriState.TRUE, beanConverter=MyCustomConverter.class)
+class MyTest {
+ @Test
+ @BctConfig(sortCollections=TriState.TRUE)
+ void testSomething() {
+ // sortMaps=true (from class), sortCollections=true (from method),
+ // beanConverter=MyCustomConverter (from class)
+ assertBean(order, "items{0{name}}", "{{Laptop}}");
+ }
+}
+```
+
+The annotation supports:
+- **sortMaps**: Enable sorting of map entries before comparison
+- **sortCollections**: Enable sorting of collection elements before comparison
+- **beanConverter**: Specify a custom bean converter class to use
+
+For more details, see [Customization](07.03.00.Customization.md).
+
### Custom Bean Converters
-The default bean converter can be customized on a per-thread basis using
`setConverter()` and `resetConverter()`. This is particularly useful in test
setup methods to configure a custom converter for all tests in a test class or
method.
+The default bean converter can be customized on a per-thread basis using
`BctConfiguration.set(BeanConverter)` or via the `@BctConfig` annotation. This
is particularly useful in test setup methods to configure a custom converter
for all tests in a test class or method.
```java
// Set custom converter in @BeforeEach method
@@ -551,7 +576,7 @@ var converter = BasicBeanConverter.builder()
.addStringifier(Money.class, money ->
money.getAmount().toPlainString())
.build();
- BctAssertions.setConverter(converter);
+ BctConfiguration.set(converter);
}
// All assertions now use the custom converter
@@ -563,7 +588,7 @@ void testWithCustomConverter() {
// Reset in @AfterEach method
@AfterEach
void tearDown() {
- BctAssertions.resetConverter();
+ BctConfiguration.clear();
}
```
@@ -573,11 +598,12 @@ void tearDown() {
For detailed information on extending and customizing BCT, see:
-- **[Stringifiers](07.01.01.Stringifiers.md)** - Define how objects are
converted to strings for comparison
-- **[Listifiers](07.01.02.Listifiers.md)** - Convert collection-like objects
to lists for testing
-- **[Swappers](07.01.03.Swappers.md)** - Transform objects before processing
(unwrap Optional, Supplier, etc.)
+- **[Custom Error Messages](07.02.00.CustomErrorMessages.md)** - Add
contextual information to assertion failures
+- **[Customization](07.03.00.Customization.md)** - Configure BCT via
@BctConfig annotation and BctConfiguration class
+ - **[Stringifiers](07.03.01.Stringifiers.md)** - Define how objects are
converted to strings for comparison
+ - **[Listifiers](07.03.02.Listifiers.md)** - Convert collection-like objects
to lists for testing
+ - **[Swappers](07.03.03.Swappers.md)** - Transform objects before processing
(unwrap Optional, Supplier, etc.)
- **[Property Extractors](07.01.04.PropertyExtractors.md)** - Define custom
property access logic for non-standard objects
-- **[Custom Error Messages](07.01.05.CustomErrorMessages.md)** - Add
contextual information to assertion failures
## Migration Examples
diff --git a/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
b/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
index 4f24da3162..1a9752c23f 100644
--- a/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
+++ b/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
@@ -480,8 +480,9 @@ try {
## See Also
-- [Stringifiers](07.01.01.Stringifiers.md) - Converting objects to strings
-- [Listifiers](07.01.02.Listifiers.md) - Converting collection-like objects to
lists
-- [Swappers](07.01.03.Swappers.md) - Transforming objects before processing
- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Stringifiers](07.03.01.Stringifiers.md) - Converting objects to strings
+ - [Listifiers](07.03.02.Listifiers.md) - Converting collection-like objects
to lists
+ - [Swappers](07.03.03.Swappers.md) - Transforming objects before processing
diff --git a/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
b/juneau-docs/docs/topics/07.02.00.CustomErrorMessages.md
similarity index 96%
rename from juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
rename to juneau-docs/docs/topics/07.02.00.CustomErrorMessages.md
index 119ddd2e4a..0a73632cbd 100644
--- a/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
+++ b/juneau-docs/docs/topics/07.02.00.CustomErrorMessages.md
@@ -398,9 +398,10 @@ void testOrderIntegration() {
## See Also
-- [Stringifiers](07.01.01.Stringifiers.md) - Converting objects to strings
-- [Listifiers](07.01.02.Listifiers.md) - Converting collection-like objects to
lists
-- [Swappers](07.01.03.Swappers.md) - Transforming objects before processing
-- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Stringifiers](07.03.01.Stringifiers.md) - Converting objects to strings
+ - [Listifiers](07.03.02.Listifiers.md) - Converting collection-like objects
to lists
+ - [Swappers](07.03.03.Swappers.md) - Transforming objects before processing
+- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
diff --git a/juneau-docs/docs/topics/07.03.00.Customization.md
b/juneau-docs/docs/topics/07.03.00.Customization.md
new file mode 100644
index 0000000000..43b1db7ac8
--- /dev/null
+++ b/juneau-docs/docs/topics/07.03.00.Customization.md
@@ -0,0 +1,351 @@
+---
+title: "Customization"
+slug: Customization
+---
+
+# 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](#bctconfig-annotation)
+- [BctConfiguration Class](#bctconfiguration-class)
+- [Configuration Properties](#configuration-properties)
+- [Custom Bean Converters](#custom-bean-converters)
+- [Thread Safety](#thread-safety)
+- [Advanced Customization Topics](#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
+
+```java
+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.
+
+```java
+@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.
+
+```java
+@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.
+
+```java
+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.
+
+```java
+@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`:
+
+```java
+@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
+
+```java
+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
+
+```java
+// 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.
+
+```java
+// 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.
+
+```java
+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.
+
+```java
+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
+
+```java
+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
+
+```java
+@BctConfig(beanConverter=MyCustomConverter.class)
+class MyTest {
+ @Test
+ void testWithCustomConverter() {
+ assertBean(order, "date,total", "2023-12-01,99.99");
+ }
+}
+```
+
+### Using via Programmatic API
+
+```java
+@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
+
+```java
+// 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:
+
+```java
+@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](07.03.01.Stringifiers.md)** - Define how objects are
converted to strings for comparison
+- **[Listifiers](07.03.02.Listifiers.md)** - Convert collection-like objects
to lists for testing
+- **[Swappers](07.03.03.Swappers.md)** - Transform objects before processing
(unwrap Optional, Supplier, etc.)
+
+## See Also
+
+- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Custom Error Messages](07.02.00.CustomErrorMessages.md) - Enhanced error
reporting
+- [Property Extractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
+
diff --git a/juneau-docs/docs/topics/07.01.01.Stringifiers.md
b/juneau-docs/docs/topics/07.03.01.Stringifiers.md
similarity index 95%
rename from juneau-docs/docs/topics/07.01.01.Stringifiers.md
rename to juneau-docs/docs/topics/07.03.01.Stringifiers.md
index ac587d3b58..dda0ec41eb 100644
--- a/juneau-docs/docs/topics/07.01.01.Stringifiers.md
+++ b/juneau-docs/docs/topics/07.03.01.Stringifiers.md
@@ -190,8 +190,9 @@ try {
## See Also
-- [Listifiers](07.01.02.Listifiers.md) - Converting collection-like objects to
lists
-- [Swappers](07.01.03.Swappers.md) - Transforming objects before processing
-- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Listifiers](07.03.02.Listifiers.md) - Converting collection-like objects
to lists
+ - [Swappers](07.03.03.Swappers.md) - Transforming objects before processing
+- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
diff --git a/juneau-docs/docs/topics/07.01.02.Listifiers.md
b/juneau-docs/docs/topics/07.03.02.Listifiers.md
similarity index 96%
rename from juneau-docs/docs/topics/07.01.02.Listifiers.md
rename to juneau-docs/docs/topics/07.03.02.Listifiers.md
index 6f9de86b2d..d3bed0b58e 100644
--- a/juneau-docs/docs/topics/07.01.02.Listifiers.md
+++ b/juneau-docs/docs/topics/07.03.02.Listifiers.md
@@ -259,8 +259,9 @@ Listifier<DataSource> safeListifier = (conv, source) -> {
## See Also
-- [Stringifiers](07.01.01.Stringifiers.md) - Converting objects to strings
-- [Swappers](07.01.03.Swappers.md) - Transforming objects before processing
-- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Stringifiers](07.03.01.Stringifiers.md) - Converting objects to strings
+ - [Swappers](07.03.03.Swappers.md) - Transforming objects before processing
+- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
diff --git a/juneau-docs/docs/topics/07.01.03.Swappers.md
b/juneau-docs/docs/topics/07.03.03.Swappers.md
similarity index 97%
rename from juneau-docs/docs/topics/07.01.03.Swappers.md
rename to juneau-docs/docs/topics/07.03.03.Swappers.md
index ee060bd61b..df5429e2b4 100644
--- a/juneau-docs/docs/topics/07.01.03.Swappers.md
+++ b/juneau-docs/docs/topics/07.03.03.Swappers.md
@@ -355,8 +355,9 @@ Swapper<Validation> validationSwapper = (conv, validation)
-> {
## See Also
-- [Stringifiers](07.01.01.Stringifiers.md) - Converting objects to strings
-- [Listifiers](07.01.02.Listifiers.md) - Converting collection-like objects to
lists
-- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
- [juneau-bct Basics](07.01.00.JuneauBctBasics.md) - Main BCT documentation
+- [Customization](07.03.00.Customization.md) - Configuration via @BctConfig
and BctConfiguration
+ - [Stringifiers](07.03.01.Stringifiers.md) - Converting objects to strings
+ - [Listifiers](07.03.02.Listifiers.md) - Converting collection-like objects
to lists
+- [PropertyExtractors](07.01.04.PropertyExtractors.md) - Custom property
access logic
diff --git a/juneau-docs/sidebars.ts b/juneau-docs/sidebars.ts
index f54e0c96f2..4013176709 100644
--- a/juneau-docs/sidebars.ts
+++ b/juneau-docs/sidebars.ts
@@ -863,29 +863,41 @@ const sidebars: SidebarsConfig = {
},
{
type: 'doc',
- id: 'topics/07.01.01.Stringifiers',
- label: '7.1.1. Stringifiers',
+ id: 'topics/07.02.00.CustomErrorMessages',
+ label: '7.2. Custom Error Messages',
},
{
- type: 'doc',
- id: 'topics/07.01.02.Listifiers',
- label: '7.1.2. Listifiers',
- },
- {
- type: 'doc',
- id: 'topics/07.01.03.Swappers',
- label: '7.1.3. Swappers',
+ type: 'category',
+ label: '7.3. Customization',
+ collapsed: true,
+ items: [
+ {
+ type: 'doc',
+ id: 'topics/07.03.00.Customization',
+ label: '7.3. Customization',
+ },
+ {
+ type: 'doc',
+ id: 'topics/07.03.01.Stringifiers',
+ label: '7.3.1. Stringifiers',
+ },
+ {
+ type: 'doc',
+ id: 'topics/07.03.02.Listifiers',
+ label: '7.3.2. Listifiers',
+ },
+ {
+ type: 'doc',
+ id: 'topics/07.03.03.Swappers',
+ label: '7.3.3. Swappers',
+ },
+ ],
},
{
type: 'doc',
id: 'topics/07.01.04.PropertyExtractors',
label: '7.1.4. Property Extractors',
},
- {
- type: 'doc',
- id: 'topics/07.01.05.CustomErrorMessages',
- label: '7.1.5. Custom Error Messages',
- },
],
},
{
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanSession_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/BeanSession_Test.java
index 7ab35555c5..657af8756b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanSession_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanSession_Test.java
@@ -16,8 +16,6 @@
*/
package org.apache.juneau;
-import org.junit.jupiter.api.*;
-
class BeanSession_Test extends TestBase {
// Test class for BeanSession
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/HeaderInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/HeaderInfo_Test.java
index 9f39d8fe08..fbfd02f2e3 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/HeaderInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/HeaderInfo_Test.java
@@ -221,7 +221,7 @@ class HeaderInfo_Test extends TestBase {
}
@Test void c07_keySet() {
- assertList(TESTER.bean().keySet(), "allowEmptyValue",
"allowReserved", "deprecated", "description", "examples", "explode",
"required", "schema", "style", "x-example", "x1", "x2");
+ assertList(TESTER.bean().keySet(), "allowEmptyValue",
"allowReserved", "deprecated", "description", "examples", "explode",
"required", "schema", "x-example", "style", "x1", "x2");
}
@Test void c08_get() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Items_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Items_Test.java
index a9cd1c6c1d..cd43cb75ad 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Items_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Items_Test.java
@@ -271,7 +271,7 @@ class Items_Test extends TestBase {
}
@Test void c07_keySet() {
- assertList(TESTER.bean().keySet(), "additionalItems",
"allOf", "collectionFormat", "default", "discriminator", "enum", "example",
"exclusiveMaximum", "exclusiveMinimum", "externalDocs", "format", "items",
"maxItems", "maxLength", "maxProperties", "maximum", "minItems", "minLength",
"minProperties", "minimum", "multipleOf", "pattern", "properties", "readOnly",
"required", "title", "type", "uniqueItems", "x1", "x2", "xml");
+ assertList(TESTER.bean().keySet(), "collectionFormat",
"default", "enum", "exclusiveMaximum", "exclusiveMinimum", "format", "items",
"maxItems", "maxLength", "maximum", "minItems", "minLength", "minimum",
"multipleOf", "pattern", "type", "uniqueItems", "additionalItems", "allOf",
"discriminator", "example", "externalDocs", "maxProperties", "minProperties",
"properties", "readOnly", "required", "title", "xml", "x1", "x2");
}
@Test void c08_get() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
index 6f6b65acf4..e6ccd983f6 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
@@ -228,7 +228,7 @@ class Parameter_Test extends TestBase {
}
@Test void c07_keySet() {
- assertList(TESTER.bean().keySet(), "allowEmptyValue",
"allowReserved", "content", "deprecated", "description", "example", "examples",
"explode", "in", "name", "required", "schema", "style", "x1", "x2");
+ assertList(TESTER.bean().keySet(), "allowEmptyValue",
"allowReserved", "deprecated", "description", "example", "examples", "explode",
"in", "name", "required", "schema", "style", "content", "x1", "x2");
}
@Test void c08_get() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/SchemaInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/SchemaInfo_Test.java
index 7e99125e2e..62bc12efc6 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/SchemaInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/SchemaInfo_Test.java
@@ -277,7 +277,7 @@ class SchemaInfo_Test extends TestBase {
}
@Test void c07_keySet() {
- assertList(TESTER.bean().keySet(), "additionalItems",
"additionalProperties", "allOf", "anyOf", "default", "description",
"discriminator", "enum", "example", "exclusiveMaximum", "exclusiveMinimum",
"externalDocs", "format", "items", "maxItems", "maxLength", "maxProperties",
"maximum", "minItems", "minLength", "minProperties", "minimum", "multipleOf",
"not", "nullable", "oneOf", "pattern", "properties", "readOnly", "required",
"title", "type", "uniqueItems", "writeOnly", "x1", "x2", "xml");
+ assertList(TESTER.bean().keySet(),
"additionalProperties", "allOf", "anyOf", "default", "description",
"discriminator", "enum", "example", "exclusiveMaximum", "exclusiveMinimum",
"externalDocs", "format", "items", "maxItems", "maxLength", "maxProperties",
"maximum", "minItems", "minLength", "minProperties", "minimum", "multipleOf",
"not", "nullable", "oneOf", "pattern", "properties", "readOnly", "required",
"title", "type", "uniqueItems", "writeOnly", "xml", "additionalItems", "x1",
"x2");
}
@Test void c08_get() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
index b4c92e38f1..b4ac43d8c0 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
@@ -306,7 +306,7 @@ class SchemaInfo_Test extends TestBase {
}
@Test void c07_keySet() {
- assertList(TESTER.bean().keySet(), "$ref",
"additionalProperties", "allOf", "default", "description", "discriminator",
"enum", "example", "exclusiveMaximum", "exclusiveMinimum", "externalDocs",
"format", "items", "maxItems", "maxLength", "maxProperties", "maximum",
"minItems", "minLength", "minProperties", "minimum", "multipleOf", "pattern",
"properties", "readOnly", "required", "requiredProperties", "title", "type",
"uniqueItems", "x1", "x2", "xml");
+ assertList(TESTER.bean().keySet(), "$ref",
"additionalProperties", "allOf", "default", "description", "discriminator",
"enum", "example", "exclusiveMaximum", "exclusiveMinimum", "externalDocs",
"format", "items", "maxItems", "maxLength", "maxProperties", "maximum",
"minItems", "minLength", "minProperties", "minimum", "multipleOf", "pattern",
"properties", "readOnly", "required", "requiredProperties", "title", "type",
"uniqueItems", "xml", "x1", "x2");
}
@Test void c08_get() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Maps_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Maps_Test.java
index 150139c615..47478b3c68 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Maps_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Maps_Test.java
@@ -16,12 +16,14 @@
*/
package org.apache.juneau.commons.collections;
+import static org.apache.juneau.commons.lang.TriState.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
import org.junit.jupiter.api.*;
class Maps_Test extends TestBase {
@@ -92,6 +94,7 @@ class Maps_Test extends TestBase {
//-----------------------------------------------------------------------------------------------------------------
@Test
+ @BctConfig(sortMaps = TRUE)
void b01_addPairs() {
var map = Maps.create(String.class, String.class)
.addPairs("host", "localhost", "port", "8080",
"protocol", "https")
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
index 4d6db8e676..b394fcab34 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.commons.collections;
+import static org.apache.juneau.commons.lang.TriState.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
@@ -23,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
import org.junit.jupiter.api.*;
class Sets_Test extends TestBase {
@@ -849,6 +851,7 @@ class Sets_Test extends TestBase {
}
@Test
+ @BctConfig(sortCollections = TRUE)
void u02_filtered_multipleFilters() {
var set = Sets.create(Integer.class)
.filtered(v -> v != null)
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BasicBeanConverter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BasicBeanConverter_Test.java
index 2409c601ba..a3e9d6aa32 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BasicBeanConverter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BasicBeanConverter_Test.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.junit.bct;
+import static org.apache.juneau.commons.lang.TriState.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
import static org.apache.juneau.junit.bct.BasicBeanConverter.*;
@@ -29,6 +30,7 @@ import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
import org.junit.jupiter.api.*;
/**
@@ -172,6 +174,7 @@ class BasicBeanConverter_Test extends TestBase {
@Test
@DisplayName("b04_stringify() handles collections")
+ @BctConfig(sortCollections = TRUE)
void b04_stringify_handlesCollections() {
assertEquals("[1,2,3]", converter.stringify(l(1, 2,
3)));
assertEquals("[]",
converter.stringify(Collections.emptyList()));
@@ -216,6 +219,7 @@ class BasicBeanConverter_Test extends TestBase {
@Test
@DisplayName("b09_listify() handles collections")
+ @BctConfig(sortCollections = TRUE)
void b09_listify_handlesCollections() {
var set = Set.of("z", "a", "m");
var result = converter.listify(set);
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
index 92961f8778..cff53d2307 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
@@ -20,12 +20,14 @@ import static
org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.juneau.commons.lang.TriState.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
import org.junit.jupiter.api.*;
import org.opentest4j.*;
@@ -392,6 +394,7 @@ class BctAssertions_Test extends TestBase {
//
====================================================================================================
@Nested
+ @BctConfig(sortMaps = TRUE)
class H_assertMap extends TestBase {
@Test
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfig_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfig_Test.java
new file mode 100644
index 0000000000..2a3dbaa560
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctConfig_Test.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.junit.bct;
+
+import static org.apache.juneau.commons.lang.TriState.*;
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.apache.juneau.junit.bct.BctConfiguration.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Unit tests for {@link BctConfig} annotation and extension.
+ */
+class BctConfig_Test extends TestBase {
+
+ //
====================================================================================================
+ // Class-level annotation tests
+ //
====================================================================================================
+
+ @Nested
+ @BctConfig(sortMaps = TRUE)
+ class A_classLevelAnnotation extends TestBase {
+
+ @Test
+ void a01_sortMapsEnabled() {
+ // Verify sortMaps is enabled from class-level
annotation
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ }
+
+ @Test
+ void a02_sortCollectionsNotEnabled() {
+ // Verify sortCollections is not enabled (default UNSET)
+ assertFalse(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+ }
+
+ @Nested
+ @BctConfig(sortMaps = TRUE, sortCollections = TRUE)
+ class B_classLevelBothEnabled extends TestBase {
+
+ @Test
+ void b01_bothEnabled() {
+ // Verify both are enabled from class-level annotation
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+ }
+
+ //
====================================================================================================
+ // Method-level annotation tests
+ //
====================================================================================================
+
+ @Nested
+ class C_methodLevelAnnotation extends TestBase {
+
+ @Test
+ @BctConfig(sortMaps = TRUE)
+ void c01_sortMapsEnabled() {
+ // Verify sortMaps is enabled from method-level
annotation
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ }
+
+ @Test
+ @BctConfig(sortCollections = TRUE)
+ void c02_sortCollectionsEnabled() {
+ // Verify sortCollections is enabled from method-level
annotation
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+
+ @Test
+ void c03_noAnnotation() {
+ // Verify no settings are enabled when no annotation is
present
+ assertFalse(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertFalse(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+ }
+
+ //
====================================================================================================
+ // Method-level overriding class-level tests
+ //
====================================================================================================
+
+ @Nested
+ @BctConfig(sortMaps = TRUE, sortCollections = TRUE)
+ class D_methodOverridesClass extends TestBase {
+
+ @Test
+ @BctConfig(sortMaps = FALSE)
+ void d01_methodOverridesSortMaps() {
+ // Method-level should override class-level for sortMaps
+ assertFalse(BctConfiguration.get(BCT_SORT_MAPS, false));
+ // sortCollections should still be true from
class-level (UNSET in method inherits from class)
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+
+ @Test
+ @BctConfig(sortCollections = FALSE)
+ void d02_methodOverridesSortCollections() {
+ // sortMaps should still be true from class-level
(UNSET in method inherits from class)
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ // Method-level should override class-level for
sortCollections
+ assertFalse(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+
+ @Test
+ @BctConfig(sortMaps = FALSE, sortCollections = FALSE)
+ void d03_methodOverridesBoth() {
+ // Method-level should override both from class-level
+ assertFalse(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertFalse(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+
+ @Test
+ void d04_inheritsFromClass() {
+ // No method annotation, should inherit from class
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+ }
+
+ //
====================================================================================================
+ // BeanConverter annotation tests
+ //
====================================================================================================
+
+ /**
+ * Custom converter for testing.
+ */
+ static class TestBeanConverter extends BasicBeanConverter {
+ public TestBeanConverter() {
+ super(BasicBeanConverter.builder().defaultSettings());
+ }
+ }
+
+ @Nested
+ class E_beanConverterAnnotation extends TestBase {
+
+ @Test
+ @BctConfig(beanConverter = TestBeanConverter.class)
+ void e01_customConverterSet() {
+ // Verify custom converter is set by using it in an
assertion
+ // The converter should be used by assertBean
+ var person = new TestPerson("Alice", 25);
+ assertDoesNotThrow(() -> assertBean(person, "name",
"Alice"));
+ // Verify it's actually the custom converter by
checking the class
+ var converter = BctConfiguration.getConverter();
+ assertNotNull(converter);
+ assertEquals(TestBeanConverter.class,
converter.getClass());
+ }
+
+ @Test
+ void e02_defaultConverter() {
+ // Verify default converter is used when no annotation
+ var converter = BctConfiguration.getConverter();
+ assertNotNull(converter);
+ assertEquals(BasicBeanConverter.class,
converter.getClass());
+ }
+ }
+
+ @Nested
+ @BctConfig(beanConverter = TestBeanConverter.class)
+ class F_classLevelBeanConverter extends TestBase {
+
+ @Test
+ void f01_inheritsConverterFromClass() {
+ // Verify converter is inherited from class-level
annotation
+ var converter = BctConfiguration.getConverter();
+ assertNotNull(converter);
+ assertEquals(TestBeanConverter.class,
converter.getClass());
+ }
+
+ @Test
+ @BctConfig(beanConverter = BeanConverter.class)
+ void f02_methodFallsBackToClassConverter() {
+ // Method-level with BeanConverter.class (default)
falls back to class-level converter
+ var converter = BctConfiguration.getConverter();
+ assertNotNull(converter);
+ assertEquals(TestBeanConverter.class,
converter.getClass());
+ }
+
+ @Test
+ @BctConfig(beanConverter = BasicBeanConverter.class)
+ void f03_methodExplicitlyUsesDefaultConverter() {
+ // Method-level with BasicBeanConverter.class
explicitly uses default converter,
+ // overriding class-level TestBeanConverter
+ var converter = BctConfiguration.getConverter();
+ assertNotNull(converter);
+ assertEquals(BasicBeanConverter.class,
converter.getClass());
+ }
+ }
+
+ //
====================================================================================================
+ // Combined settings tests
+ //
====================================================================================================
+
+ @Nested
+ @BctConfig(sortMaps = TRUE, beanConverter = TestBeanConverter.class)
+ class G_combinedSettings extends TestBase {
+
+ @Test
+ void g01_bothSettingsApplied() {
+ // Verify both sortMaps and converter are set
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ var converter = BctConfiguration.getConverter();
+ assertEquals(TestBeanConverter.class,
converter.getClass());
+ }
+
+ @Test
+ @BctConfig(sortCollections = TRUE)
+ void g02_methodAddsToClass() {
+ // Method adds sortCollections, inherits sortMaps and
converter from class
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ var converter = BctConfiguration.getConverter();
+ assertEquals(TestBeanConverter.class,
converter.getClass());
+ }
+ }
+
+ //
====================================================================================================
+ // Clearing after test tests
+ //
====================================================================================================
+
+ @Nested
+ class H_clearingAfterTest extends TestBase {
+
+ @Test
+ @BctConfig(sortMaps = TRUE, sortCollections = TRUE)
+ void h01_settingsClearedAfterTest() {
+ // Settings should be active during test
+ assertTrue(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertTrue(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+
+ @AfterEach
+ void verifyCleared() {
+ // After test, settings should be cleared
+ // Note: This runs after the extension's afterEach, so
we need to check in the next test
+ }
+
+ @Test
+ void h02_verifyPreviousTestCleared() {
+ // This test runs after h01, so settings should be
cleared
+ assertFalse(BctConfiguration.get(BCT_SORT_MAPS, false));
+ assertFalse(BctConfiguration.get(BCT_SORT_COLLECTIONS,
false));
+ }
+ }
+
+ //
====================================================================================================
+ // Error handling tests
+ //
====================================================================================================
+
+ @Nested
+ class I_errorHandling extends TestBase {
+
+ /**
+ * Converter without no-arg constructor (should fail).
+ */
+ static class InvalidConverter extends BasicBeanConverter {
+ public InvalidConverter(String arg) {
+
super(BasicBeanConverter.builder().defaultSettings());
+ }
+ }
+ }
+
+ //
====================================================================================================
+ // Test helper classes
+ //
====================================================================================================
+
+ /**
+ * Test person class for testing.
+ */
+ static class TestPerson {
+ private final String name;
+ private final int age;
+
+ TestPerson(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ String getName() { return name; }
+
+ int getAge() { return age; }
+
+ @Override
+ public String toString() {
+ return "TestPerson{name='" + name + "', age=" + age +
"}";
+ }
+ }
+}
+
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/Listifiers_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/Listifiers_Test.java
index 7088d5454f..811625c79b 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/Listifiers_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/Listifiers_Test.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.junit.bct;
+import static org.apache.juneau.commons.lang.TriState.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
@@ -25,6 +26,7 @@ import java.util.*;
import java.util.stream.*;
import org.apache.juneau.*;
+import org.apache.juneau.junit.bct.annotations.*;
import org.junit.jupiter.api.*;
/**
@@ -46,20 +48,21 @@ class Listifiers_Test extends TestBase {
}
@Test
+ @BctConfig(sortCollections = TRUE)
void a02_listifySet() {
var listifier = Listifiers.collectionListifier();
var input = Set.of("z", "a", "m"); // Unordered input
var result = listifier.apply(null, input);
-
- // TreeSet conversion ensures natural ordering
assertList(result, "a", "m", "z");
}
@Test
+ @BctConfig(sortCollections = TRUE)
void a02a_listifySetTypes() {
var listifier = Listifiers.collectionListifier();
// HashSet (unordered) -> converted to TreeSet for
natural ordering
+ BctConfiguration.set(BctConfiguration.BCT_SORT_MAPS,
true);
var hashSet = new HashSet<>(l("z", "a", "m"));
var hashResult = listifier.apply(null, hashSet);
assertList(hashResult, "a", "m", "z");
@@ -298,6 +301,7 @@ class Listifiers_Test extends TestBase {
class F_mapListifier extends TestBase {
@Test
+ @BctConfig(sortMaps = TRUE)
void f01_listifyMap() {
var listifier = Listifiers.mapListifier();
var input = m("z", "value1", "a", "value2"); //
Unordered input
@@ -314,6 +318,7 @@ class Listifiers_Test extends TestBase {
}
@Test
+ @BctConfig(sortMaps = TRUE)
void f01a_listifyMapTypes() {
var listifier = Listifiers.mapListifier();