001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.junit.bct; 018 019import static java.util.stream.Collectors.*; 020import static org.apache.juneau.junit.bct.Utils.*; 021import static org.junit.jupiter.api.Assertions.*; 022 023import java.util.*; 024import java.util.function.*; 025import java.util.stream.*; 026 027import org.opentest4j.*; 028 029/** 030 * Comprehensive utility class for Bean-Centric Tests (BCT) and general testing operations. 031 * 032 * <p>This class extends the functionality provided by the JUnit Assertions class, with particular emphasis 033 * on the Bean-Centric Testing (BCT) framework. BCT enables sophisticated assertion patterns for 034 * testing object properties, collections, maps, and complex nested structures with minimal code.</p> 035 * 036 * <h5 class='section'>Bean-Centric Testing (BCT) Framework:</h5> 037 * <p>The BCT framework consists of several key components:</p> 038 * <ul> 039 * <li><b>{@link BeanConverter}:</b> Core interface for object conversion and property access</li> 040 * <li><b>{@link BasicBeanConverter}:</b> Default implementation with extensible type handlers</li> 041 * <li><b>Assertion Methods:</b> High-level testing methods that leverage the converter framework</li> 042 * </ul> 043 * 044 * <h5 class='section'>Primary BCT Assertion Methods:</h5> 045 * <dl> 046 * <dt><b>{@link #assertBean(Object, String, String)}</b></dt> 047 * <dd>Tests object properties with nested syntax support and collection iteration</dd> 048 * 049 * <dt><b>{@link #assertBeans(Collection, String, String...)}</b></dt> 050 * <dd>Tests collections of objects by extracting and comparing specific fields</dd> 051 * 052 * <dt><b>{@link #assertMapped(Object, java.util.function.BiFunction, String, String)}</b></dt> 053 * <dd>Tests custom property access using BiFunction for non-standard objects</dd> 054 * 055 * <dt><b>{@link #assertList(List, Object...)}</b></dt> 056 * <dd>Tests list/collection elements with varargs for expected values</dd> 057 * </dl> 058 * 059 * <h5 class='section'>BCT Advanced Features:</h5> 060 * <ul> 061 * <li><b>Nested Property Syntax:</b> "address{street,city}" for testing nested objects</li> 062 * <li><b>Collection Iteration:</b> "#{address{street,city}}" syntax for testing all elements</li> 063 * <li><b>Universal Size Properties:</b> "length" and "size" work on all collection types</li> 064 * <li><b>Array/List Access:</b> Numeric indices for element-specific testing</li> 065 * <li><b>Method Chaining:</b> Fluent setters can be tested directly</li> 066 * <li><b>Direct Field Access:</b> Public fields accessed without getters</li> 067 * <li><b>Map Key Access:</b> Including special <js>"<null>"</js> syntax for null keys</li> 068 * </ul> 069 * 070 * <h5 class='section'>Converter Extensibility:</h5> 071 * <p>The BCT framework is built on the extensible {@link BasicBeanConverter} which allows:</p> 072 * <ul> 073 * <li><b>Custom Stringifiers:</b> Type-specific string conversion logic</li> 074 * <li><b>Custom Listifiers:</b> Collection-type conversion for iteration</li> 075 * <li><b>Custom Swappers:</b> Object transformation before conversion</li> 076 * <li><b>Custom PropertyExtractors:</b> Property extraction</li> 077 * <li><b>Configurable Settings:</b> Formatting, delimiters, and display options</li> 078 * </ul> 079 * 080 * <h5 class='section'>Usage Examples:</h5> 081 * 082 * <p><b>Basic Property Testing:</b></p> 083 * <p class='bjava'> 084 * <jc>// Test multiple properties</jc> 085 * <jsm>assertBean</jsm>(<jv>user</jv>, <js>"name,age,active"</js>, <js>"John,30,true"</js>); 086 * 087 * <jc>// Test nested properties - user has getAddress() returning Address with getStreet() and getCity()</jc> 088 * <jsm>assertBean</jsm>(<jv>user</jv>, <js>"name,address{street,city}"</js>, <js>"John,{123 Main St,Springfield}"</js>); 089 * </p> 090 * 091 * <p><b>Collection and Array Testing:</b></p> 092 * <p class='bjava'> 093 * <jc>// Test collection size and iterate over all elements - order has getItems() returning List<Product> where Product has getName()</jc> 094 * <jsm>assertBean</jsm>(<jv>order</jv>, <js>"items{length,#{name}}"</js>, <js>"{3,[{Laptop},{Phone},{Tablet}]}"</js>); 095 * 096 * <jc>// Test specific array elements - listOfData is a List<DataObject> where DataObject has getData()</jc> 097 * <jsm>assertBean</jsm>(<jv>listOfData</jv>, <js>"0{data},1{data}"</js>, <js>"{100},{200}"</js>); 098 * </p> 099 * 100 * <p><b>Collection Testing:</b></p> 101 * <p class='bjava'> 102 * <jc>// Test list elements</jc> 103 * <jsm>assertList</jsm>(tags, <js>"red"</js>, <js>"green"</js>, <js>"blue"</js>); 104 * 105 * <jc>// Test map entries using assertBean</jc> 106 * <jsm>assertBean</jsm>(<jv>config</jv>, <js>"timeout,retries"</js>, <js>"30000,3"</js>); 107 * </p> 108 * 109 * <p><b>Custom Property Access:</b></p> 110 * <p class='bjava'> 111 * <jc>// Test with custom accessor function</jc> 112 * <jsm>assertMapped</jsm>(<jv>myObject</jv>, (<jp>obj</jp>, <jp>prop</jp>) -> <jp>obj</jp>.getProperty(<jp>prop</jp>), 113 * <js>"prop1,prop2"</js>, <js>"value1,value2"</js>); 114 * </p> 115 * 116 * <h5 class='section'>Performance and Thread Safety:</h5> 117 * <p>The BCT framework is designed for high performance with:</p> 118 * <ul> 119 * <li><b>Caching:</b> Type-to-handler mappings cached for fast lookup</li> 120 * <li><b>Thread Safety:</b> All operations are thread-safe for concurrent testing</li> 121 * <li><b>Minimal Allocation:</b> Efficient object reuse and minimal temporary objects</li> 122 * </ul> 123 * 124 * @see BeanConverter 125 * @see BasicBeanConverter 126 */ 127public class BctAssertions { 128 129 private static final BeanConverter DEFAULT_CONVERTER = BasicBeanConverter.DEFAULT; 130 131 private BctAssertions() {} 132 133 /** 134 * Creates a new {@link AssertionArgs} instance for configuring assertion behavior. 135 * 136 * <p>AssertionArgs provides fluent configuration for customizing assertion behavior, including:</p> 137 * <ul> 138 * <li><b>Custom Messages:</b> Static strings, parameterized with <code>MessageFormat</code>, or dynamic suppliers</li> 139 * <li><b>Custom Bean Converters:</b> Override default object-to-string conversion behavior</li> 140 * <li><b>Timeout Configuration:</b> Set timeouts for operations that may take time</li> 141 * </ul> 142 * 143 * <h5 class='section'>Usage Examples:</h5> 144 * <p class='bjava'> 145 * <jc>// Static message</jc> 146 * <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"User validation failed"</js>), 147 * <jv>user</jv>, <js>"name,age"</js>, <js>"John,30"</js>); 148 * 149 * <jc>// Parameterized message</jc> 150 * <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"Test failed for user {0}"</js>, <jv>userId</jv>), 151 * <jv>user</jv>, <js>"status"</js>, <js>"ACTIVE"</js>); 152 * 153 * <jc>// Dynamic message with supplier</jc> 154 * <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(() -> <js>"Test failed at "</js> + Instant.<jsm>now</jsm>()), 155 * <jv>result</jv>, <js>"success"</js>, <js>"true"</js>); 156 * 157 * <jc>// Custom bean converter</jc> 158 * <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>() 159 * .defaultSettings() 160 * .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> <jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>)) 161 * .build(); 162 * <jsm>assertBean</jsm>(<jsm>args</jsm>().setBeanConverter(<jv>converter</jv>), 163 * <jv>event</jv>, <js>"date"</js>, <js>"2023-12-01"</js>); 164 * </p> 165 * 166 * @return A new AssertionArgs instance for fluent configuration 167 * @see AssertionArgs 168 */ 169 public static AssertionArgs args() { 170 return new AssertionArgs(); 171 } 172 173 /** 174 * Asserts that the fields/properties on the specified bean are the specified values after being converted to strings. 175 * 176 * <p>This is the primary method for Bean-Centric Tests (BCT), supporting extensive property validation 177 * patterns including nested objects, collections, arrays, method chaining, direct field access, collection iteration 178 * with <js>"#{property}"</js> syntax, and universal <js>"length"</js>/<js>"size"</js> properties for all collection types.</p> 179 * 180 * <p>The method uses the {@link BasicBeanConverter#DEFAULT} converter internally for object introspection 181 * and value extraction. The converter provides sophisticated property access through the {@link BeanConverter} 182 * interface, supporting multiple fallback mechanisms for accessing object properties and values.</p> 183 * 184 * <h5 class='section'>Basic Usage:</h5> 185 * <p class='bjava'> 186 * <jc>// Test multiple properties</jc> 187 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"prop1,prop2,prop3"</js>, <js>"val1,val2,val3"</js>); 188 * 189 * <jc>// Test single property</jc> 190 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name"</js>, <js>"John"</js>); 191 * </p> 192 * 193 * <h5 class='section'>Nested Property Testing:</h5> 194 * <p class='bjava'> 195 * <jc>// Test nested bean properties</jc> 196 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name,address{street,city,state}"</js>, <js>"John,{123 Main St,Springfield,IL}"</js>); 197 * 198 * <jc>// Test arbitrarily deep nesting</jc> 199 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name,person{address{geo{lat,lon}}}"</js>, <js>"John,{{{40.7,-74.0}}}"</js>); 200 * </p> 201 * 202 * <h5 class='section'>Array, List, and Stream Testing:</h5> 203 * <p class='bjava'> 204 * <jc>// Test array/list elements by index - items is a String[] or List<String></jc> 205 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"items{0,1,2}"</js>, <js>"{item1,item2,item3}"</js>); 206 * 207 * <jc>// Test nested properties within array elements - orders is a List<Order> where Order has getId() and getTotal()</jc> 208 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"orders{0{id,total}}"</js>, <js>"{{123,99.95}}"</js>); 209 * 210 * <jc>// Test array length property - items can be any array or collection type</jc> 211 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"items{length}"</js>, <js>"{5}"</js>); 212 * 213 * <jc>// Works with any iterable type including Streams - userStream returns a Stream<User> where User has getName()</jc> 214 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"userStream{#{name}}"</js>, <js>"[{Alice},{Bob}]"</js>); 215 * </p> 216 * 217 * <h5 class='section'>Collection Iteration Syntax:</h5> 218 * <p class='bjava'> 219 * <jc>// Test properties across ALL elements in a collection using #{...} syntax - userList is a List<User> where User has getName()</jc> 220 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"userList{#{name}}"</js>, <js>"[{John},{Jane},{Bob}]"</js>); 221 * 222 * <jc>// Test multiple properties from each element - orderList is a List<Order> where Order has getId() and getStatus()</jc> 223 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"orderList{#{id,status}}"</js>, <js>"[{123,ACTIVE},{124,PENDING}]"</js>); 224 * 225 * <jc>// Works with nested properties within each element - customers is a List<Customer> where Customer has getAddress() returning Address with getCity()</jc> 226 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"customers{#{address{city}}}"</js>, <js>"[{{New York}},{{Los Angeles}}]"</js>); 227 * 228 * <jc>// Works with arrays and any iterable collection type (including Streams)</jc> 229 * <jsm>assertBean</jsm>(<jv>config</jv>, <js>"itemArray{#{type}}"</js>, <js>"[{String},{Integer},{Boolean}]"</js>); 230 * <jsm>assertBean</jsm>(<jv>data</jv>, <js>"statusSet{#{name}}"</js>, <js>"[{ACTIVE},{PENDING},{CANCELLED}]"</js>); 231 * <jsm>assertBean</jsm>(<jv>processor</jv>, <js>"dataStream{#{value}}"</js>, <js>"[{A},{B},{C}]"</js>); 232 * </p> 233 * 234 * <h5 class='section'>Universal Collection Size Properties:</h5> 235 * <p class='bjava'> 236 * <jc>// Both 'length' and 'size' work universally across all collection types</jc> 237 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myArray{length}"</js>, <js>"{5}"</js>); <jc>// Arrays</jc> 238 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myArray{size}"</js>, <js>"{5}"</js>); <jc>// Also works for arrays</jc> 239 * 240 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myList{size}"</js>, <js>"{3}"</js>); <jc>// Collections</jc> 241 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myList{length}"</js>, <js>"{3}"</js>); <jc>// Also works for collections</jc> 242 * 243 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myMap{size}"</js>, <js>"{7}"</js>); <jc>// Maps</jc> 244 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"myMap{length}"</js>, <js>"{7}"</js>); <jc>// Also works for maps</jc> 245 * </p> 246 * 247 * <h5 class='section'>Class Name Testing:</h5> 248 * <p class='bjava'> 249 * <jc>// Test class properties (prefer simple names for maintainability)</jc> 250 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"obj{class{simpleName}}"</js>, <js>"{{MyClass}}"</js>); 251 * 252 * <jc>// Test full class names when needed</jc> 253 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"obj{class{name}}"</js>, <js>"{{com.example.MyClass}}"</js>); 254 * </p> 255 * 256 * <h5 class='section'>Method Chaining Support:</h5> 257 * <p class='bjava'> 258 * <jc>// Test fluent setter chains (returns same object)</jc> 259 * <jsm>assertBean</jsm>( 260 * <jv>item</jv>.setType(<js>"foo"</js>).setFormat(<js>"bar"</js>).setDefault(<js>"baz"</js>), 261 * <js>"type,format,default"</js>, 262 * <js>"foo,bar,baz"</js> 263 * ); 264 * </p> 265 * 266 * <h5 class='section'>Advanced Collection Analysis:</h5> 267 * <p class='bjava'> 268 * <jc>// Combine size/length, metadata, and content iteration in single assertions - users is a List<User></jc> 269 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"users{length,class{simpleName},#{name}}"</js>, 270 * <js>"{3,{ArrayList},[{John},{Jane},{Bob}]}"</js>); 271 * 272 * <jc>// Comprehensive collection validation with multiple iteration patterns - items is a List<Product> where Product has getName() and getPrice()</jc> 273 * <jsm>assertBean</jsm>(<jv>order</jv>, <js>"items{size,#{name},#{price}}"</js>, 274 * <js>"{3,[{Laptop},{Phone},{Tablet}],[{999.99},{599.99},{399.99}]}"</js>); 275 * 276 * <jc>// Perfect for validation testing - verify error count and details; errors is a List<ValidationError> where ValidationError has getField() and getCode()</jc> 277 * <jsm>assertBean</jsm>(<jv>result</jv>, <js>"errors{length,#{field},#{code}}"</js>, 278 * <js>"{2,[{email},{password}],[{E001},{E002}]}"</js>); 279 * 280 * <jc>// Mixed collection types with consistent syntax - results and metadata are different collection types</jc> 281 * <jsm>assertBean</jsm>(<jv>response</jv>, <js>"results{size},metadata{length}"</js>, <js>"{25},{4}"</js>); 282 * </p> 283 * 284 * <h5 class='section'>Direct Field Access:</h5> 285 * <p class='bjava'> 286 * <jc>// Test public fields directly (no getters required)</jc> 287 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"f1,f2,f3"</js>, <js>"val1,val2,val3"</js>); 288 * 289 * <jc>// Test field properties with chaining</jc> 290 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"f1{length},f2{class{simpleName}}"</js>, <js>"{5},{{String}}"</js>); 291 * </p> 292 * 293 * <h5 class='section'>Map Testing:</h5> 294 * <p class='bjava'> 295 * <jc>// Test map values by key</jc> 296 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"configMap{timeout,retries}"</js>, <js>"{30000,3}"</js>); 297 * 298 * <jc>// Test map size</jc> 299 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"settings{size}"</js>, <js>"{5}"</js>); 300 * 301 * <jc>// Test null keys using special <null> syntax</jc> 302 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"mapWithNullKey{<null>}"</js>, <js>"{nullKeyValue}"</js>); 303 * </p> 304 * 305 * <h5 class='section'>Collection and Boolean Values:</h5> 306 * <p class='bjava'> 307 * <jc>// Test boolean values</jc> 308 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"enabled,visible"</js>, <js>"true,false"</js>); 309 * 310 * <jc>// Test enum collections</jc> 311 * <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"statuses"</js>, <js>"[ACTIVE,PENDING]"</js>); 312 * </p> 313 * 314 * <h5 class='section'>Value Syntax Rules:</h5> 315 * <ul> 316 * <li><b>Simple values:</b> <js>"value"</js> for direct property values</li> 317 * <li><b>Nested values:</b> <js>"{value}"</js> for single-level nested properties</li> 318 * <li><b>Deep nested values:</b> <js>"{{value}}"</js>, <js>"{{{value}}}"</js> for multiple nesting levels</li> 319 * <li><b>Array/Collection values:</b> <js>"[item1,item2]"</js> for collections</li> 320 * <li><b>Collection iteration:</b> <js>"#{property}"</js> iterates over ALL collection elements, returns <js>"[{val1},{val2}]"</js></li> 321 * <li><b>Universal size properties:</b> <js>"length"</js> and <js>"size"</js> work on arrays, collections, and maps</li> 322 * <li><b>Boolean values:</b> <js>"true"</js>, <js>"false"</js></li> 323 * <li><b>Null values:</b> <js>"null"</js></li> 324 * </ul> 325 * 326 * <h5 class='section'>Property Access Priority:</h5> 327 * <ol> 328 * <li><b>Collection/Array access:</b> Numeric indices for arrays/lists (e.g., <js>"0"</js>, <js>"1"</js>)</li> 329 * <li><b>Universal size properties:</b> <js>"length"</js> and <js>"size"</js> for arrays, collections, and maps</li> 330 * <li><b>Map key access:</b> Direct key lookup for Map objects (including <js>"<null>"</js> for null keys)</li> 331 * <li><b>is{Property}()</b> methods (for boolean properties)</li> 332 * <li><b>get{Property}()</b> methods</li> 333 * <li><b>Public fields</b> (direct field access)</li> 334 * </ol> 335 * 336 * @param actual The bean object to test. Must not be null. 337 * @param fields Comma-delimited list of property names to test. Supports nested syntax with {}. 338 * @param expected Comma-delimited list of expected values. Must match the order of fields. 339 * @throws NullPointerException if the bean is null 340 * @throws AssertionError if any property values don't match expected values 341 * @see BeanConverter 342 * @see BasicBeanConverter 343 */ 344 public static void assertBean(Object actual, String fields, String expected) { 345 assertBean(args(), actual, fields, expected); 346 } 347 348 /** 349 * Same as {@link #assertBean(Object, String, String)} but with configurable assertion behavior. 350 * 351 * @param args Assertion configuration. See {@link #args()} for usage examples. 352 * @param actual The bean to test. Must not be null. 353 * @param fields A comma-delimited list of bean property names (supports nested syntax). 354 * @param expected The expected property values as a comma-delimited string. 355 * @see #assertBean(Object, String, String) 356 * @see #args() 357 */ 358 public static void assertBean(AssertionArgs args, Object actual, String fields, String expected) { 359 assertNotNull(actual, "Actual was null."); 360 assertArgNotNull("args", args); 361 assertArgNotNull("fields", fields); 362 assertArgNotNull("expected", expected); 363 assertEquals( 364 expected, 365 tokenize(fields).stream().map(x -> args.getBeanConverter().orElse(DEFAULT_CONVERTER).getNested(actual, x)).collect(joining(",")), 366 args.getMessage("Bean assertion failed.") 367 ); 368 } 369 370 /** 371 * Asserts that multiple beans in a collection have the expected property values. 372 * 373 * <p>This method validates that each bean in a collection has the specified property values, 374 * using the same property access logic as {@link #assertBean(Object, String, String)}. 375 * It's perfect for testing collections of similar objects or validation results.</p> 376 * 377 * <h5 class='section'>Basic Usage:</h5> 378 * <p class='bjava'> 379 * <jc>// Test list of user beans</jc> 380 * <jsm>assertBeans</jsm>(<jv>userList</jv>, <js>"name,age"</js>, 381 * <js>"John,25"</js>, <js>"Jane,30"</js>, <js>"Bob,35"</js>); 382 * </p> 383 * 384 * <h5 class='section'>Complex Property Testing:</h5> 385 * <p class='bjava'> 386 * <jc>// Test nested properties across multiple beans - orderList is a List<Order> where Order has getId() and getCustomer() returning Customer with getName() and getEmail()</jc> 387 * <jsm>assertBeans</jsm>(<jv>orderList</jv>, <js>"id,customer{name,email}"</js>, 388 * <js>"1,{John,john@example.com}"</js>, 389 * <js>"2,{Jane,jane@example.com}"</js>); 390 * 391 * <jc>// Test collection properties within beans - cartList is a List<ShoppingCart> where ShoppingCart has getItems() returning List<Product> and getTotal()</jc> 392 * <jsm>assertBeans</jsm>(<jv>cartList</jv>, <js>"items{0{name}},total"</js>, 393 * <js>"{{Laptop}},999.99"</js>, 394 * <js>"{{Phone}},599.99"</js>); 395 * </p> 396 * 397 * <h5 class='section'>Validation Testing:</h5> 398 * <p class='bjava'> 399 * <jc>// Test validation results</jc> 400 * <jsm>assertBeans</jsm>(<jv>validationErrors</jv>, <js>"field,message,code"</js>, 401 * <js>"email,Invalid email format,E001"</js>, 402 * <js>"age,Must be 18 or older,E002"</js>); 403 * </p> 404 * 405 * <h5 class='section'>Collection Iteration Testing:</h5> 406 * <p class='bjava'> 407 * <jc>// Test collection iteration within beans (#{...} syntax)</jc> 408 * <jsm>assertBeans</jsm>(<jv>departmentList</jv>, <js>"name,employees{#{name}}"</js>, 409 * <js>"Engineering,[{Alice},{Bob},{Charlie}]"</js>, 410 * <js>"Marketing,[{David},{Eve}]"</js>); 411 * </p> 412 * 413 * <h5 class='section'>Parser Result Testing:</h5> 414 * <p class='bjava'> 415 * <jc>// Test parsed object collections</jc> 416 * <jk>var</jk> <jv>parsed</jv> = JsonParser.<jsf>DEFAULT</jsf>.parse(<jv>jsonArray</jv>, MyBean[].class); 417 * <jsm>assertBeans</jsm>(<jsm>Arrays.asList</jsm>(<jv>parsed</jv>), <js>"prop1,prop2"</js>, 418 * <js>"val1,val2"</js>, <js>"val3,val4"</js>); 419 * </p> 420 * 421 * @param actual The collection of beans to check. Must not be null. 422 * @param fields A comma-delimited list of bean property names (supports nested syntax). 423 * @param expected Array of expected value strings, one per bean. Each string contains comma-delimited values matching the fields. 424 * @throws AssertionError if the collection size doesn't match values array length or if any bean properties don't match 425 * @see #assertBean(Object, String, String) 426 */ 427 public static void assertBeans(Object actual, String fields, String...expected) { 428 assertBeans(args(), actual, fields, expected); 429 } 430 431 /** 432 * Same as {@link #assertBeans(Object, String, String...)} but with configurable assertion behavior. 433 * 434 * @param args Assertion configuration. See {@link #args()} for usage examples. 435 * @param actual The collection of beans to test. Must not be null. 436 * @param fields A comma-delimited list of bean property names (supports nested syntax). 437 * @param expected Array of expected value strings, one per bean. 438 * @see #assertBeans(Object, String, String...) 439 * @see #args() 440 */ 441 public static void assertBeans(AssertionArgs args, Object actual, String fields, String...expected) { 442 assertNotNull(actual, "Value was null."); 443 assertArgNotNull("args", args); 444 assertArgNotNull("fields", fields); 445 assertArgNotNull("expected", expected); 446 447 var converter = args.getBeanConverter().orElse(DEFAULT_CONVERTER); 448 var tokens = tokenize(fields); 449 var errors = new ArrayList<AssertionFailedError>(); 450 var actualList = converter.listify(actual); 451 452 if (ne(expected.length, actualList.size())) { 453 errors.add(assertEqualsFailed(expected.length, actualList.size(), args.getMessage("Wrong number of beans."))); 454 } else { 455 for (var i = 0; i < actualList.size(); i++) { 456 var i2 = i; 457 var e = converter.stringify(expected[i]); 458 var a = tokens.stream().map(x -> converter.getNested(actualList.get(i2), x)).collect(joining(",")); 459 if (ne(e, a)) { 460 errors.add(assertEqualsFailed(e, a, args.getMessage("Bean at row <{0}> did not match.", i))); 461 } 462 } 463 } 464 465 if (errors.isEmpty()) return; 466 467 var actualStrings = new ArrayList<String>(); 468 for (var o : actualList) { 469 actualStrings.add(tokens.stream().map(x -> converter.getNested(o, x)).collect(joining(","))); 470 } 471 472 throw assertEqualsFailed( 473 Stream.of(expected).map(Utils::escapeForJava).collect(joining("\", \"", "\"", "\"")), 474 actualStrings.stream().map(Utils::escapeForJava).collect(joining("\", \"", "\"", "\"")), 475 args.getMessage("{0} bean assertions failed:\n{1}", errors.size(), errors.stream().map(x -> x.getMessage()).collect(joining("\n"))) 476 ); 477 } 478 479 /** 480 * Asserts that mapped property access on an object returns expected values using a custom BiFunction. 481 * 482 * <p>This is designed for testing objects that don't follow 483 * standard JavaBean patterns or require custom property access logic. The BiFunction allows complete 484 * control over how properties are retrieved from the target object.</p> 485 * 486 * <p>This method creates an intermediate LinkedHashMap to collect all property values before 487 * using the same logic as assertBean for comparison. This ensures consistent ordering 488 * and supports the full nested property syntax. The {@link BasicBeanConverter#DEFAULT} is used 489 * for value stringification and nested property access.</p> 490 * 491 * @param <T> The type of object being tested 492 * @param actual The object to test properties on 493 * @param function The BiFunction that extracts property values. Receives (<jp>object</jp>, <jp>propertyName</jp>) and returns the property value. 494 * @param properties Comma-delimited list of property names to test 495 * @param expected Comma-delimited list of expected values (exceptions become simple class names) 496 * @throws AssertionError if any mapped property values don't match expected values 497 * @see #assertBean(Object, String, String) 498 * @see BeanConverter 499 * @see BasicBeanConverter 500 */ 501 public static <T> void assertMapped(T actual, BiFunction<T,String,Object> function, String properties, String expected) { 502 assertMapped(args(), actual, function, properties, expected); 503 } 504 505 /** 506 * Same as {@link #assertMapped(Object, BiFunction, String, String)} but with configurable assertion behavior. 507 * 508 * @param <T> The object type being tested. 509 * @param args Assertion configuration. See {@link #args()} for usage examples. 510 * @param actual The object to test. Must not be null. 511 * @param function Custom property access function. 512 * @param properties A comma-delimited list of property names. 513 * @param expected The expected property values as a comma-delimited string. 514 * @see #assertMapped(Object, BiFunction, String, String) 515 * @see #args() 516 */ 517 public static <T> void assertMapped(AssertionArgs args, T actual, BiFunction<T,String,Object> function, String properties, String expected) { 518 assertNotNull(actual, "Value was null."); 519 assertArgNotNull("args", args); 520 assertArgNotNull("function", function); 521 assertArgNotNull("properties", properties); 522 assertArgNotNull("expected", expected); 523 524 var m = new LinkedHashMap<String,Object>(); 525 for (var p : tokenize(properties)) { 526 var pv = p.getValue(); 527 m.put(pv, safe(() -> function.apply(actual, pv))); 528 } 529 530 assertBean(args, m, properties, expected); 531 } 532 533 /** 534 * Asserts that the string representation of an object contains the expected substring. 535 * 536 * <p>This method converts the actual object to its string representation using the current 537 * {@link BeanConverter} and then checks if it contains the expected substring. This is useful 538 * for testing partial content matches without requiring exact string equality.</p> 539 * 540 * <h5 class='section'>Usage Examples:</h5> 541 * <p class='bjava'> 542 * <jc>// Test that error message contains key information</jc> 543 * <jsm>assertContains</jsm>(<js>"FileNotFoundException"</js>, <jv>exception</jv>); 544 * 545 * <jc>// Test that object string representation contains expected data</jc> 546 * <jsm>assertContains</jsm>(<js>"status=ACTIVE"</js>, <jv>user</jv>); 547 * 548 * <jc>// Test partial JSON/XML content</jc> 549 * <jsm>assertContains</jsm>(<js>"\"name\":\"John\""</js>, <jv>jsonResponse</jv>); 550 * </p> 551 * 552 * @param expected The substring that must be present in the actual object's string representation 553 * @param actual The object to test. Must not be null. 554 * @throws AssertionError if the actual object is null or its string representation doesn't contain the expected substring 555 * @see #assertContainsAll(Object, String...) for multiple substring assertions 556 * @see #assertString(String, Object) for exact string matching 557 */ 558 public static void assertContains(String expected, Object actual) { 559 assertContains(args(), expected, actual); 560 } 561 562 /** 563 * Same as {@link #assertContains(String, Object)} but with configurable assertion behavior. 564 * 565 * @param args Assertion configuration. See {@link #args()} for usage examples. 566 * @param expected The substring that must be present. 567 * @param actual The object to test. Must not be null. 568 * @see #assertContains(String, Object) 569 * @see #args() 570 */ 571 public static void assertContains(AssertionArgs args, String expected, Object actual) { 572 assertArgNotNull("args", args); 573 assertArgNotNull("expected", expected); 574 assertArgNotNull("actual", actual); 575 assertNotNull(actual, "Value was null."); 576 577 var a = args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual); 578 assertTrue(a.contains(expected), args.getMessage("String did not contain expected substring. ==> expected: <{0}> but was: <{1}>", expected, a)); 579 } 580 581 /** 582 * Asserts that the string representation of an object contains all specified substrings. 583 * 584 * <p>This method is similar to {@link #assertContains(String, Object)} but tests for multiple 585 * required substrings. All provided substrings must be present in the actual object's string 586 * representation for the assertion to pass.</p> 587 * 588 * <h5 class='section'>Usage Examples:</h5> 589 * <p class='bjava'> 590 * <jc>// Test that error contains multiple pieces of information</jc> 591 * <jsm>assertContainsAll</jsm>(<jv>exception</jv>, <js>"FileNotFoundException"</js>, <js>"config.xml"</js>, <js>"/etc"</js>); 592 * 593 * <jc>// Test that user object contains expected fields</jc> 594 * <jsm>assertContainsAll</jsm>(<jv>user</jv>, <js>"name=John"</js>, <js>"age=30"</js>, <js>"status=ACTIVE"</js>); 595 * 596 * <jc>// Test log output contains all required entries</jc> 597 * <jsm>assertContainsAll</jsm>(<jv>logOutput</jv>, <js>"INFO"</js>, <js>"Started"</js>, <js>"Successfully"</js>); 598 * </p> 599 * 600 * @param actual The object to test. Must not be null. 601 * @param expected Multiple substrings that must all be present in the actual object's string representation 602 * @throws AssertionError if the actual object is null or its string representation doesn't contain all expected substrings 603 * @see #assertContains(String, Object) for single substring assertions 604 */ 605 public static void assertContainsAll(Object actual, String...expected) { 606 assertContainsAll(args(), actual, expected); 607 } 608 609 /** 610 * Same as {@link #assertContainsAll(Object, String...)} but with configurable assertion behavior. 611 * 612 * @param args Assertion configuration. See {@link #args()} for usage examples. 613 * @param actual The object to test. Must not be null. 614 * @param expected Multiple substrings that must all be present. 615 * @see #assertContainsAll(Object, String...) 616 * @see #args() 617 */ 618 public static void assertContainsAll(AssertionArgs args, Object actual, String...expected) { 619 assertArgNotNull("args", args); 620 assertArgNotNull("expected", expected); 621 assertNotNull(actual, "Value was null."); 622 623 var a = args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual); 624 var errors = new ArrayList<AssertionFailedError>(); 625 626 for (var e : expected) { 627 if (!a.contains(e)) { 628 errors.add(assertEqualsFailed(true, false, args.getMessage("String did not contain expected substring. ==> expected: <{0}> but was: <{1}>", e, a))); 629 } 630 } 631 632 if (errors.isEmpty()) return; 633 634 if (errors.size() == 1) throw errors.get(0); 635 636 var missingSubstrings = new ArrayList<String>(); 637 for (var e : expected) { 638 if (!a.contains(e)) { 639 missingSubstrings.add(e); 640 } 641 } 642 643 throw assertEqualsFailed( 644 missingSubstrings.stream().map(Utils::escapeForJava).collect(joining("\", \"", "\"", "\"")), 645 Utils.escapeForJava(a), 646 args.getMessage("{0} substring assertions failed:\n{1}", errors.size(), errors.stream().map(x -> x.getMessage()).collect(joining("\n"))) 647 ); 648 } 649 650 /** 651 * Asserts that a collection-like object or Optional is not null and empty. 652 * 653 * <p>This method validates that the provided object is empty according to its type:</p> 654 * <ul> 655 * <li><b>Optional:</b> Must be empty (not present)</li> 656 * <li><b>Map:</b> Must have no entries</li> 657 * <li><b>Collection-like objects:</b> Must be convertible to an empty List via {@link BeanConverter#listify(Object)}</li> 658 * </ul> 659 * 660 * <h5 class='section'>Supported Types:</h5> 661 * <p>Any object that can be converted to a List, including:</p> 662 * <ul> 663 * <li>Collections (List, Set, Queue, etc.)</li> 664 * <li>Arrays (primitive and object arrays)</li> 665 * <li>Iterables, Iterators, Streams</li> 666 * <li>Maps (converted to list of entries)</li> 667 * <li>Optional objects</li> 668 * </ul> 669 * 670 * <h5 class='section'>Usage Examples:</h5> 671 * <p class='bjava'> 672 * <jc>// Test empty collections</jc> 673 * <jsm>assertEmpty</jsm>(Collections.<jsm>emptyList</jsm>()); 674 * <jsm>assertEmpty</jsm>(<jk>new</jk> ArrayList<>()); 675 * 676 * <jc>// Test empty arrays</jc> 677 * <jsm>assertEmpty</jsm>(<jk>new</jk> String[0]); 678 * 679 * <jc>// Test empty Optional</jc> 680 * <jsm>assertEmpty</jsm>(Optional.<jsm>empty</jsm>()); 681 * 682 * <jc>// Test empty Map</jc> 683 * <jsm>assertEmpty</jsm>(<jk>new</jk> HashMap<>()); 684 * </p> 685 * 686 * @param value The object to test. Must not be null. 687 * @throws AssertionError if the object is null or not empty 688 * @see #assertNotEmpty(Object) for testing non-empty collections 689 * @see #assertSize(int, Object) for testing specific sizes 690 */ 691 public static void assertEmpty(Object value) { 692 assertEmpty(args(), value); 693 } 694 695 /** 696 * Same as {@link #assertEmpty(Object)} but with configurable assertion behavior. 697 * 698 * @param args Assertion configuration. See {@link #args()} for usage examples. 699 * @param value The object to test. Must not be null. 700 * @see #assertEmpty(Object) 701 * @see #args() 702 */ 703 public static void assertEmpty(AssertionArgs args, Object value) { 704 assertArgNotNull("args", args); 705 assertNotNull(value, "Value was null."); 706 707 if (value instanceof Optional<?> v2) { 708 assertTrue(v2.isEmpty(), "Optional was not empty"); 709 return; 710 } 711 712 if (value instanceof Map<?,?> v2) { 713 assertTrue(v2.isEmpty(), "Map was not empty"); 714 return; 715 } 716 717 var converter = args.getBeanConverter().orElse(DEFAULT_CONVERTER); 718 719 assertTrue(converter.canListify(value), args.getMessage("Value cannot be converted to a list. Class=<{0}>", value.getClass().getSimpleName())); 720 assertTrue(converter.listify(value).isEmpty(), args.getMessage("Value was not empty.")); 721 } 722 723 /** 724 * Asserts that a List or List-like object contains the expected values using flexible comparison logic. 725 * 726 * <h5 class='section'>Testing Non-List Collections:</h5> 727 * <p class='bjava'> 728 * <jc>// Test a Set using l() conversion</jc> 729 * Set<String> <jv>mySet</jv> = <jk>new</jk> TreeSet<>(Arrays.<jsm>asList</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>)); 730 * <jsm>assertList</jsm>(<jsm>l</jsm>(<jv>mySet</jv>), <js>"a"</js>, <js>"b"</js>, <js>"c"</js>); 731 * 732 * <jc>// Test an array using l() conversion</jc> 733 * String[] <jv>myArray</jv> = {<js>"x"</js>, <js>"y"</js>, <js>"z"</js>}; 734 * <jsm>assertList</jsm>(<jsm>l</jsm>(<jv>myArray</jv>), <js>"x"</js>, <js>"y"</js>, <js>"z"</js>); 735 * 736 * <jc>// Test a Stream using l() conversion</jc> 737 * Stream<String> <jv>myStream</jv> = Stream.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>); 738 * <jsm>assertList</jsm>(<jsm>l</jsm>(<jv>myStream</jv>), <js>"foo"</js>, <js>"bar"</js>); 739 * </p> 740 * 741 * <h5 class='section'>Comparison Modes:</h5> 742 * <p>The method supports three different ways to compare expected vs actual values:</p> 743 * 744 * <h6 class='section'>1. String Comparison (Readable Format):</h6> 745 * <p class='bjava'> 746 * <jc>// Elements are converted to strings using the bean converter and compared as strings</jc> 747 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(1, 2, 3), <js>"1"</js>, <js>"2"</js>, <js>"3"</js>); 748 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>), <js>"a"</js>, <js>"b"</js>); 749 * </p> 750 * 751 * <h6 class='section'>2. Predicate Testing (Functional Validation):</h6> 752 * <p class='bjava'> 753 * <jc>// Use Predicate<T> for functional testing</jc> 754 * Predicate<Integer> <jv>greaterThanOne</jv> = <jv>x</jv> -> <jv>x</jv> > 1; 755 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(2, 3, 4), <jv>greaterThanOne</jv>, <jv>greaterThanOne</jv>, <jv>greaterThanOne</jv>); 756 * 757 * <jc>// Mix predicates with other comparison types</jc> 758 * Predicate<String> <jv>startsWithA</jv> = <jv>s</jv> -> <jv>s</jv>.startsWith(<js>"a"</js>); 759 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(<js>"apple"</js>, <js>"banana"</js>), <jv>startsWithA</jv>, <js>"banana"</js>); 760 * </p> 761 * 762 * <h6 class='section'>3. Object Equality (Direct Comparison):</h6> 763 * <p class='bjava'> 764 * <jc>// Non-String, non-Predicate objects use <jsm>Objects.equals</jsm>() comparison</jc> 765 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(1, 2, 3), 1, 2, 3); <jc>// Integer objects</jc> 766 * <jsm>assertList</jsm>(List.<jsm>of</jsm>(<jv>myBean1</jv>, <jv>myBean2</jv>), <jv>myBean1</jv>, <jv>myBean2</jv>); <jc>// Custom objects</jc> 767 * </p> 768 * 769 * @param actual The List to test. Must not be null. 770 * @param expected Multiple arguments of expected values. 771 * Can be Strings (readable format comparison), Predicates (functional testing), or Objects (direct equality). 772 * @throws AssertionError if the List size or contents don't match expected values 773 */ 774 public static void assertList(Object actual, Object...expected) { 775 assertList(args(), actual, expected); 776 } 777 778 /** 779 * Asserts that a Map contains the expected key/value pairs using flexible comparison logic. 780 * 781 * <h5 class='section'>Map Entry Serialization:</h5> 782 * <p>Map entries are serialized to strings as key/value pairs in the format <js>"key=value"</js>. 783 * Nested maps and collections are supported with appropriate formatting.</p> 784 * 785 * <h5 class='section'>Testing Nested Maps and Collections:</h5> 786 * <p class='bjava'> 787 * <jc>// Test simple map entries</jc> 788 * Map<String,String> <jv>simpleMap</jv> = Map.<jsm>of</jsm>(<js>"a"</js>, <js>"1"</js>, <js>"b"</js>, <js>"2"</js>); 789 * <jsm>assertMap</jsm>(<jv>simpleMap</jv>, <js>"a=1"</js>, <js>"b=2"</js>); 790 * 791 * <jc>// Test nested maps</jc> 792 * Map<String,Map<String,Integer>> <jv>nestedMap</jv> = Map.<jsm>of</jsm>(<js>"a"</js>, Map.<jsm>of</jsm>(<js>"b"</js>, 1)); 793 * <jsm>assertMap</jsm>(<jv>nestedMap</jv>, <js>"a={b=1}"</js>); 794 * 795 * <jc>// Test maps with arrays/collections</jc> 796 * Map<String,Map<String,Integer[]>> <jv>mapWithArrays</jv> = Map.<jsm>of</jsm>(<js>"a"</js>, Map.<jsm>of</jsm>(<js>"b"</js>, <jk>new</jk> Integer[]{1,2})); 797 * <jsm>assertMap</jsm>(<jv>mapWithArrays</jv>, <js>"a={b=[1,2]}"</js>); 798 * </p> 799 * 800 * <h5 class='section'>Comparison Modes:</h5> 801 * <p>The method supports the same comparison modes as {@link #assertList(Object, Object...)}:</p> 802 * 803 * <h6 class='section'>1. String Comparison (Readable Format):</h6> 804 * <p class='bjava'> 805 * <jc>// Map entries are converted to strings and compared as strings</jc> 806 * <jsm>assertMap</jsm>(Map.<jsm>of</jsm>(<js>"key1"</js>, <js>"value1"</js>), <js>"key1=value1"</js>); 807 * <jsm>assertMap</jsm>(Map.<jsm>of</jsm>(<js>"count"</js>, 42), <js>"count=42"</js>); 808 * </p> 809 * 810 * <h6 class='section'>2. Predicate Testing (Functional Validation):</h6> 811 * <p class='bjava'> 812 * <jc>// Use Predicate<Map.Entry<K,V>> for functional testing</jc> 813 * Predicate<Map.Entry<String,Integer>> <jv>valueGreaterThanTen</jv> = <jv>entry</jv> -> <jv>entry</jv>.getValue() > 10; 814 * <jsm>assertMap</jsm>(Map.<jsm>of</jsm>(<js>"count"</js>, 42), <jv>valueGreaterThanTen</jv>); 815 * </p> 816 * 817 * <h6 class='section'>3. Object Equality (Direct Comparison):</h6> 818 * <p class='bjava'> 819 * <jc>// Non-String, non-Predicate objects use <jsm>Objects.equals</jsm>() comparison</jc> 820 * <jsm>assertMap</jsm>(Map.<jsm>of</jsm>(<js>"key"</js>, <jv>myObject</jv>), <jv>expectedEntry</jv>); 821 * </p> 822 * 823 * <h5 class='section'>Map Ordering Behavior:</h5> 824 * <p>The {@link Listifiers#mapListifier()} method ensures deterministic ordering for map entries:</p> 825 * <ul> 826 * <li><b>{@link SortedMap} (TreeMap, etc.):</b> Preserves existing sort order</li> 827 * <li><b>{@link LinkedHashMap}:</b> Preserves insertion order</li> 828 * <li><b>{@link HashMap} and other unordered Maps:</b> Converts to {@link TreeMap} for natural key ordering</li> 829 * </ul> 830 * <p>This ensures predictable test results regardless of the original map implementation.</p> 831 * 832 * @param actual The Map to test. Must not be null. 833 * @param expected Multiple arguments of expected map entries. 834 * Can be Strings (readable format comparison), Predicates (functional testing), or Objects (direct equality). 835 * @throws AssertionError if the Map size or contents don't match expected values 836 * @see #assertList(Object, Object...) 837 */ 838 public static void assertMap(Map<?,?> actual, Object...expected) { 839 assertList(args(), actual, expected); 840 } 841 842 /** 843 * Same as {@link #assertMap(Map, Object...)} but with configurable assertion behavior. 844 * 845 * @param args Assertion configuration. See {@link #args()} for usage examples. 846 * @param actual The Map to test. Must not be null. 847 * @param expected Multiple arguments of expected map entries. 848 * @see #assertMap(Map, Object...) 849 * @see #args() 850 */ 851 public static void assertMap(AssertionArgs args, Map<?,?> actual, Object...expected) { 852 assertList(args, actual, expected); 853 } 854 855 /** 856 * Same as {@link #assertList(Object, Object...)} but with configurable assertion behavior. 857 * 858 * @param args Assertion configuration. See {@link #args()} for usage examples. 859 * @param actual The List to test. Must not be null. 860 * @param expected Multiple arguments of expected values. 861 * @see #assertList(Object, Object...) 862 * @see #args() 863 */ 864 public static void assertList(AssertionArgs args, Object actual, Object...expected) { 865 assertArgNotNull("args", args); 866 assertArgNotNull("expected", expected); 867 assertNotNull(actual, "Value was null."); 868 869 var converter = args.getBeanConverter().orElse(DEFAULT_CONVERTER); 870 var list = converter.listify(actual); 871 var errors = new ArrayList<AssertionFailedError>(); 872 873 if (ne(expected.length, list.size())) { 874 errors.add(assertEqualsFailed(expected.length, list.size(), args.getMessage("Wrong list length."))); 875 } else { 876 for (var i = 0; i < expected.length; i++) { 877 var x = list.get(i); 878 var e = expected[i]; 879 if (e instanceof String e2) { 880 if (ne(e2, converter.stringify(x))) { 881 errors.add(assertEqualsFailed(e2, converter.stringify(x), args.getMessage("Element at index {0} did not match.", i))); 882 } 883 } else if (e instanceof Predicate e2) { // NOSONAR 884 if (!e2.test(x)) { 885 errors.add(new AssertionFailedError(args.getMessage("Element at index {0} did not pass predicate. ==> actual: <{1}>", i, converter.stringify(x)).get())); 886 } 887 } else { 888 if (ne(e, x)) { 889 errors.add(assertEqualsFailed(e, x, args.getMessage("Element at index {0} did not match. ==> expected: <{1}({2})> but was: <{3}({4})>", i, e, t(e), x, t(x)))); 890 } 891 } 892 } 893 } 894 895 if (errors.isEmpty()) return; 896 897 var actualStrings = new ArrayList<String>(); 898 for (var o : list) { 899 actualStrings.add(converter.stringify(o)); 900 } 901 902 if (errors.size() == 1) throw errors.get(0); 903 904 throw assertEqualsFailed( 905 Stream.of(expected).map(converter::stringify).map(Utils::escapeForJava).collect(joining("\", \"", "[\"", "\"]")), 906 actualStrings.stream().map(Utils::escapeForJava).collect(joining("\", \"", "[\"", "\"]")), 907 args.getMessage("{0} list assertions failed:\n{1}", errors.size(), errors.stream().map(x -> x.getMessage()).collect(joining("\n"))) 908 ); 909 } 910 911 /** 912 * Asserts that a collection-like object or Optional is not null and not empty. 913 * 914 * <p>This method validates that the provided object is not empty according to its type:</p> 915 * <ul> 916 * <li><b>Optional:</b> Must be present (not empty)</li> 917 * <li><b>Map:</b> Must have at least one entry</li> 918 * <li><b>Collection-like objects:</b> Must convert to a non-empty List via {@link BeanConverter#listify(Object)}</li> 919 * </ul> 920 * 921 * <h5 class='section'>Supported Types:</h5> 922 * <p>Any object that can be converted to a List, including:</p> 923 * <ul> 924 * <li>Collections (List, Set, Queue, etc.)</li> 925 * <li>Arrays (primitive and object arrays)</li> 926 * <li>Iterables, Iterators, Streams</li> 927 * <li>Maps (converted to list of entries)</li> 928 * <li>Optional objects</li> 929 * </ul> 930 * 931 * <h5 class='section'>Usage Examples:</h5> 932 * <p class='bjava'> 933 * <jc>// Test non-empty collections</jc> 934 * <jsm>assertNotEmpty</jsm>(List.<jsm>of</jsm>(<js>"item1"</js>, <js>"item2"</js>)); 935 * <jsm>assertNotEmpty</jsm>(<jk>new</jk> ArrayList<>(Arrays.<jsm>asList</jsm>(<js>"a"</js>))); 936 * 937 * <jc>// Test non-empty arrays</jc> 938 * <jsm>assertNotEmpty</jsm>(<jk>new</jk> String[]{<js>"value"</js>}); 939 * 940 * <jc>// Test present Optional</jc> 941 * <jsm>assertNotEmpty</jsm>(Optional.<jsm>of</jsm>(<js>"value"</js>)); 942 * 943 * <jc>// Test non-empty Map</jc> 944 * <jsm>assertNotEmpty</jsm>(Map.<jsm>of</jsm>(<js>"key"</js>, <js>"value"</js>)); 945 * </p> 946 * 947 * @param value The object to test. Must not be null. 948 * @throws AssertionError if the object is null or empty 949 * @see #assertEmpty(Object) for testing empty collections 950 * @see #assertSize(int, Object) for testing specific sizes 951 */ 952 public static void assertNotEmpty(Object value) { 953 assertNotEmpty(args(), value); 954 } 955 956 /** 957 * Same as {@link #assertNotEmpty(Object)} but with configurable assertion behavior. 958 * 959 * @param args Assertion configuration. See {@link #args()} for usage examples. 960 * @param value The object to test. Must not be null. 961 * @see #assertNotEmpty(Object) 962 * @see #args() 963 */ 964 public static void assertNotEmpty(AssertionArgs args, Object value) { 965 assertArgNotNull("args", args); 966 assertNotNull(value, "Value was null."); 967 968 if (value instanceof Optional<?> v2) { 969 assertFalse(v2.isEmpty(), "Optional was empty"); 970 return; 971 } 972 973 if (value instanceof Map<?,?> v2) { 974 assertFalse(v2.isEmpty(), "Map was empty"); 975 return; 976 } 977 978 assertFalse(args.getBeanConverter().orElse(DEFAULT_CONVERTER).listify(value).isEmpty(), args.getMessage("Value was empty.")); 979 } 980 981 /** 982 * Asserts that a collection-like object or string is not null and of the specified size. 983 * 984 * <p>This method can validate the size of various types of objects:</p> 985 * <ul> 986 * <li><b>String:</b> Validates character length</li> 987 * <li><b>Collection-like objects:</b> Any object that can be converted to a List via the underlying converter</li> 988 * </ul> 989 * 990 * <h5 class='section'>Usage Examples:</h5> 991 * <p class='bjava'> 992 * <jc>// Test string length</jc> 993 * <jsm>assertSize</jsm>(5, <js>"hello"</js>); 994 * 995 * <jc>// Test collection size</jc> 996 * <jsm>assertSize</jsm>(3, List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>)); 997 * 998 * <jc>// Test array size</jc> 999 * <jsm>assertSize</jsm>(2, <jk>new</jk> String[]{<js>"x"</js>, <js>"y"</js>}); 1000 * </p> 1001 * 1002 * @param expected The expected size/length. 1003 * @param actual The object to test. Must not be null. 1004 * @throws AssertionError if the object is null or not the expected size. 1005 */ 1006 public static void assertSize(int expected, Object actual) { 1007 assertSize(args(), expected, actual); 1008 } 1009 1010 /** 1011 * Same as {@link #assertSize(int, Object)} but with configurable assertion behavior. 1012 * 1013 * @param args Assertion configuration. See {@link #args()} for usage examples. 1014 * @param expected The expected size/length. 1015 * @param actual The object to test. Must not be null. 1016 * @see #assertSize(int, Object) 1017 * @see #args() 1018 */ 1019 public static void assertSize(AssertionArgs args, int expected, Object actual) { 1020 assertArgNotNull("args", args); 1021 assertNotNull(actual, "Value was null."); 1022 1023 if (actual instanceof String a) { 1024 assertEquals(expected, a.length(), args.getMessage("Value not expected size. value: <{0}>", a)); 1025 return; 1026 } 1027 1028 var size = args.getBeanConverter().orElse(DEFAULT_CONVERTER).listify(actual).size(); 1029 assertEquals(expected, size, args.getMessage("Value not expected size.")); 1030 } 1031 1032 /** 1033 * Asserts that an object's string representation exactly matches the expected value. 1034 * 1035 * <p>This method converts the actual object to its string representation using the current 1036 * {@link BeanConverter} and performs an exact equality comparison with the expected string. 1037 * This is useful for testing complete string output, formatted objects, or converted values.</p> 1038 * 1039 * <h5 class='section'>Usage Examples:</h5> 1040 * <p class='bjava'> 1041 * <jc>// Test exact string conversion</jc> 1042 * <jsm>assertString</jsm>(<js>"John,30,true"</js>, <jv>user</jv>); <jc>// Assuming user converts to this format</jc> 1043 * 1044 * <jc>// Test formatted dates or numbers</jc> 1045 * <jsm>assertString</jsm>(<js>"2023-12-01"</js>, <jv>localDate</jv>); 1046 * 1047 * <jc>// Test complex object serialization</jc> 1048 * <jsm>assertString</jsm>(<js>"{name=John,age=30}"</js>, <jv>userMap</jv>); 1049 * 1050 * <jc>// Test array/collection formatting</jc> 1051 * <jsm>assertString</jsm>(<js>"[red,green,blue]"</js>, <jv>colors</jv>); 1052 * </p> 1053 * 1054 * @param expected The exact string that the actual object should convert to 1055 * @param actual The object to test. Must not be null. 1056 * @throws AssertionError if the actual object is null or its string representation doesn't exactly match expected 1057 * @see #assertContains(String, Object) for partial string matching 1058 * @see #assertMatchesGlob(String, Object) for pattern-based matching 1059 */ 1060 public static void assertString(String expected, Object actual) { 1061 assertString(args(), expected, actual); 1062 } 1063 1064 /** 1065 * Same as {@link #assertString(String, Object)} but with configurable assertion behavior. 1066 * 1067 * @param args Assertion configuration. See {@link #args()} for usage examples. 1068 * @param expected The expected string value. 1069 * @param actual The object to test. Must not be null. 1070 * @see #assertString(String, Object) 1071 * @see #args() 1072 */ 1073 public static void assertString(AssertionArgs args, String expected, Object actual) { 1074 assertArgNotNull("args", args); 1075 assertNotNull(actual, "Value was null."); 1076 1077 assertEquals(expected, args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual), args.getMessage()); 1078 } 1079 1080 /** 1081 * Asserts that an object's string representation matches the specified glob-style pattern. 1082 * 1083 * <p>This method converts the actual object to its string representation using the current 1084 * {@link BeanConverter} and then tests it against the provided glob-style pattern. 1085 * This is useful for testing string formats with simple wildcard patterns.</p> 1086 * 1087 * <h5 class='section'>Pattern Syntax:</h5> 1088 * <p>The pattern uses glob-style wildcards:</p> 1089 * <ul> 1090 * <li><b>{@code *}</b> matches any sequence of characters (including none)</li> 1091 * <li><b>{@code ?}</b> matches exactly one character</li> 1092 * <li><b>All other characters</b> are treated literally</li> 1093 * </ul> 1094 * 1095 * <h5 class='section'>Usage Examples:</h5> 1096 * <p class='bjava'> 1097 * <jc>// Test filename patterns</jc> 1098 * <jsm>assertMatchesGlob</jsm>(<js>"user_*_temp"</js>, <jv>filename</jv>); 1099 * 1100 * <jc>// Test single character wildcards</jc> 1101 * <jsm>assertMatchesGlob</jsm>(<js>"file?.txt"</js>, <jv>fileName</jv>); 1102 * 1103 * <jc>// Test combined patterns</jc> 1104 * <jsm>assertMatchesGlob</jsm>(<js>"log_*_?.txt"</js>, <jv>logFile</jv>); 1105 * </p> 1106 * 1107 * @param pattern The glob-style pattern to match against. 1108 * @param value The object to test. Must not be null. 1109 * @throws AssertionError if the value is null or its string representation doesn't match the pattern 1110 * @see #assertString(String, Object) for exact string matching 1111 * @see #assertContains(String, Object) for substring matching 1112 * @see Utils#getGlobMatchPattern(String) for pattern compilation details 1113 */ 1114 public static void assertMatchesGlob(String pattern, Object value) { 1115 assertMatchesGlob(args(), pattern, value); 1116 } 1117 1118 /** 1119 * Same as {@link #assertMatchesGlob(String, Object)} but with configurable assertion behavior. 1120 * 1121 * @param args Assertion configuration. See {@link #args()} for usage examples. 1122 * @param pattern The glob-style pattern to match against. 1123 * @param value The object to test. Must not be null. 1124 * @see #assertMatchesGlob(String, Object) 1125 * @see #args() 1126 */ 1127 public static void assertMatchesGlob(AssertionArgs args, String pattern, Object value) { 1128 assertArgNotNull("args", args); 1129 assertArgNotNull("pattern", pattern); 1130 assertNotNull(value, "Value was null."); 1131 1132 var v = args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(value); 1133 var m = getGlobMatchPattern(pattern).matcher(v); 1134 assertTrue(m.matches(), args.getMessage("Pattern didn''t match. ==> pattern: <{0}> but was: <{1}>", pattern, v)); 1135 } 1136}