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.assertions; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020import static org.apache.juneau.common.utils.ThrowableUtils.*; 021import static org.apache.juneau.common.utils.Utils.*; 022 023import java.io.*; 024import java.util.*; 025import java.util.function.*; 026 027import org.apache.juneau.common.utils.*; 028import org.apache.juneau.cp.*; 029import org.apache.juneau.internal.*; 030import org.apache.juneau.json.*; 031import org.apache.juneau.reflect.*; 032import org.apache.juneau.serializer.*; 033 034/** 035 * Used for fluent assertion calls against POJOs. 036 * 037 * <h5 class='section'>Test Methods:</h5> 038 * <p> 039 * <ul class='javatree'> 040 * <li class='jc'>{@link FluentObjectAssertion} 041 * <ul class='javatreec'> 042 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 043 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 044 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 045 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 046 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 047 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 048 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 049 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 050 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 051 * <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)} 052 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 053 * <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)} 054 * <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)} 055 * <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)} 056 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 057 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 058 * </ul> 059 * </ul> 060 * 061 * <h5 class='section'>Transform Methods:</h5> 062 * <p> 063 * <ul class='javatree'> 064 * <li class='jc'>{@link FluentObjectAssertion} 065 * <ul class='javatreec'> 066 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 067 * <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)} 068 * <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)} 069 * <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()} 070 * <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()} 071 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)} 072 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 073 * </ul> 074 * </ul> 075 * 076 * <h5 class='section'>Configuration Methods:</h5> 077 * <p> 078 * <ul class='javatree'> 079 * <li class='jc'>{@link Assertion} 080 * <ul class='javatreec'> 081 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 082 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 083 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 084 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 085 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 086 * </ul> 087 * </ul> 088 * 089 * <h5 class='section'>See Also:</h5><ul> 090 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a> 091 * </ul> 092 * 093 * @param <T> The object type. 094 * @param <R> The return type. 095 */ 096public class FluentObjectAssertion<T,R> extends FluentAssertion<R> { 097 098 //----------------------------------------------------------------------------------------------------------------- 099 // Static 100 //----------------------------------------------------------------------------------------------------------------- 101 102 private static final Messages MESSAGES = Messages.of(FluentObjectAssertion.class, "Messages"); 103 private static final String 104 MSG_unexpectedType = MESSAGES.getString("unexpectedType"), 105 MSG_unexpectedComparison = MESSAGES.getString("unexpectedComparison"), 106 MSG_unexpectedValue = MESSAGES.getString("unexpectedValue"), 107 MSG_unexpectedValueDidNotExpect = MESSAGES.getString("unexpectedValueDidNotExpect"), 108 MSG_notTheSameValue = MESSAGES.getString("notTheSameValue"), 109 MSG_valueWasNull = MESSAGES.getString("valueWasNull"), 110 MSG_valueWasNotNull = MESSAGES.getString("valueWasNotNull"), 111 MSG_expectedValueNotFound = MESSAGES.getString("expectedValueNotFound"), 112 MSG_unexpectedValueFound = MESSAGES.getString("unexpectedValueFound"), 113 MSG_unexpectedValue2 = MESSAGES.getString("unexpectedValue2"); 114 115 private static final JsonSerializer JSON = JsonSerializer.create() 116 .json5() 117 .build(); 118 119 private static final JsonSerializer JSON_SORTED = JsonSerializer.create() 120 .json5() 121 .sortProperties() 122 .sortCollections() 123 .sortMaps() 124 .build(); 125 126 //----------------------------------------------------------------------------------------------------------------- 127 // Instance 128 //----------------------------------------------------------------------------------------------------------------- 129 130 private final T value; 131 132 /** 133 * Constructor. 134 * 135 * @param value 136 * The object being tested. 137 * <br>Can be <jk>null</jk>. 138 * @param returns 139 * The object to return after a test method is called. 140 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 141 * used on the same assertion. 142 */ 143 public FluentObjectAssertion(T value, R returns) { 144 this(null, value, returns); 145 } 146 147 /** 148 * Chained constructor. 149 * 150 * <p> 151 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 152 * 153 * @param creator 154 * The assertion that created this assertion. 155 * <br>Should be <jk>null</jk> if this is the top-level assertion. 156 * @param value 157 * The object being tested. 158 * <br>Can be <jk>null</jk>. 159 * @param returns 160 * The object to return after a test method is called. 161 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 162 * used on the same assertion. 163 */ 164 public FluentObjectAssertion(Assertion creator, T value, R returns) { 165 super(creator, returns); 166 this.value = value; 167 } 168 169 //----------------------------------------------------------------------------------------------------------------- 170 // Transform methods 171 //----------------------------------------------------------------------------------------------------------------- 172 173 /** 174 * Converts this object to a string using {@link Object#toString} and returns it as a new assertion. 175 * 176 * <h5 class='section'>Example:</h5> 177 * <p class='bjava'> 178 * <jc>// Validates that the specified object is "foobar" after converting to a string.</jc> 179 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 180 * .asString() 181 * .is(<js>"foobar"</js>); 182 * </p> 183 * 184 * @return A new fluent string assertion. 185 */ 186 public FluentStringAssertion<R> asString() { 187 return new FluentStringAssertion<>(this, valueAsString(), returns()); 188 } 189 190 /** 191 * Converts this object to text using the specified serializer and returns it as a new assertion. 192 * 193 * <h5 class='section'>Example:</h5> 194 * <p class='bjava'> 195 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 196 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 197 * .asString(XmlSerializer.<jsf>DEFAULT</jsf>) 198 * .is(<js>"<object><foo>bar</foo><baz>qux</baz></object>"</js>); 199 * </p> 200 * 201 * @param ws The serializer to use to convert the object to text. 202 * @return A new fluent string assertion. 203 */ 204 public FluentStringAssertion<R> asString(WriterSerializer ws) { 205 try { 206 return new FluentStringAssertion<>(this, ws.serialize(value), returns()); 207 } catch (SerializeException e) { 208 throw asRuntimeException(e); 209 } 210 } 211 212 /** 213 * Converts this object to a string using the specified function and returns it as a new assertion. 214 * 215 * <h5 class='section'>Example:</h5> 216 * <p class='bjava'> 217 * <jc>// Validates that the specified object is "foobar" after converting to a string.</jc> 218 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 219 * .asString(<jv>x</jv>-><jv>x</jv>.toString()) 220 * .is(<js>"foobar"</js>); 221 * </p> 222 * 223 * @param function The conversion function. 224 * @return A new fluent string assertion. 225 */ 226 public FluentStringAssertion<R> asString(Function<T,String> function) { 227 return new FluentStringAssertion<>(this, function.apply(value), returns()); 228 } 229 230 /** 231 * Converts this object to simplified JSON and returns it as a new assertion. 232 * 233 * <h5 class='section'>Example:</h5> 234 * <p class='bjava'> 235 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 236 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 237 * .asJson() 238 * .is(<js>"{foo:'bar',baz:'qux'}"</js>); 239 * </p> 240 * 241 * @return A new fluent string assertion. 242 */ 243 public FluentStringAssertion<R> asJson() { 244 return asString(JSON); 245 } 246 247 /** 248 * Converts this object to sorted simplified JSON and returns it as a new assertion. 249 * 250 * <h5 class='section'>Example:</h5> 251 * <p class='bjava'> 252 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 253 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 254 * .asJsonSorted() 255 * .is(<js>"{baz:'qux',foo:'bar'}"</js>); 256 * </p> 257 * 258 * @return A new fluent string assertion. 259 */ 260 public FluentStringAssertion<R> asJsonSorted() { 261 return asString(JSON_SORTED); 262 } 263 264 /** 265 * Applies a transform on the inner object and returns a new inner object. 266 * 267 * @param function The function to apply. 268 * @return This object. 269 */ 270 public FluentObjectAssertion<T,R> asTransformed(Function<T,T> function) { // NOSONAR - Intentional. 271 return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns()); 272 } 273 274 /** 275 * Applies a transform on the inner object and returns a new inner object. 276 * 277 * @param <T2> The transform-to type. 278 * @param function The function to apply. 279 * @return This object. 280 */ 281 public <T2> FluentObjectAssertion<T2,R> asTransformedTo(Function<T,T2> function) { 282 return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns()); 283 } 284 285 /** 286 * Converts this assertion into an {@link FluentAnyAssertion} so that it can be converted to other assertion types. 287 * 288 * @return This object. 289 */ 290 public FluentAnyAssertion<T,R> asAny() { 291 return new FluentAnyAssertion<>(this, orElse(null), returns()); 292 } 293 294 //----------------------------------------------------------------------------------------------------------------- 295 // Test methods 296 //----------------------------------------------------------------------------------------------------------------- 297 298 /** 299 * Asserts that the object is not null. 300 * 301 * <p> 302 * Equivalent to {@link #isNotNull()}. 303 * 304 * @return The fluent return object. 305 * @throws AssertionError If assertion failed. 306 */ 307 public R isExists() throws AssertionError { 308 return isNotNull(); 309 } 310 311 /** 312 * Asserts that the object i null. 313 * 314 * <p> 315 * Equivalent to {@link #isNotNull()}. 316 * 317 * @return The fluent return object. 318 * @throws AssertionError If assertion failed. 319 */ 320 public R isNull() throws AssertionError { 321 if (value != null) 322 throw error(MSG_valueWasNotNull); 323 return returns(); 324 } 325 326 /** 327 * Asserts that the object is not null. 328 * 329 * <p> 330 * Equivalent to {@link #isNotNull()}. 331 * 332 * @return The fluent return object. 333 * @throws AssertionError If assertion failed. 334 */ 335 public R isNotNull() throws AssertionError { 336 if (value == null) 337 throw error(MSG_valueWasNull); 338 return returns(); 339 } 340 341 /** 342 * Asserts that the value equals the specified value. 343 * 344 * @param value The value to check against. 345 * @return The fluent return object. 346 * @throws AssertionError If assertion failed. 347 */ 348 public R is(T value) throws AssertionError { 349 if (this.value == value) 350 return returns(); 351 if (! equals(orElse(null), value)) 352 throw error(MSG_unexpectedValue, value, this.value); 353 return returns(); 354 } 355 356 /** 357 * Asserts that the value converted to a string equals the specified value. 358 * 359 * @param value The value to check against. 360 * @return The fluent return object. 361 * @throws AssertionError If assertion failed. 362 */ 363 public R isString(String value) { 364 return asString().is(value); 365 } 366 367 /** 368 * Asserts that the value does not equal the specified value. 369 * 370 * @param value The value to check against. 371 * @return The fluent return object. 372 * @throws AssertionError If assertion failed. 373 */ 374 public R isNot(T value) throws AssertionError { 375 if (equals(orElse(null), value)) 376 throw error(MSG_unexpectedValueDidNotExpect, value, orElse(null)); 377 return returns(); 378 } 379 380 /** 381 * Asserts that the value is one of the specified values. 382 * 383 * @param values The values to check against. 384 * @return The fluent return object. 385 * @throws AssertionError If assertion failed. 386 */ 387 @SafeVarargs 388 public final R isAny(T...values) throws AssertionError { 389 for (var v : values) 390 if (equals(orElse(null), v)) 391 return returns(); 392 throw error(MSG_expectedValueNotFound, values, value); 393 } 394 395 /** 396 * Asserts that the value is not one of the specified values. 397 * 398 * @param values The values to check against. 399 * @return The fluent return object. 400 * @throws AssertionError If assertion failed. 401 */ 402 @SafeVarargs 403 public final R isNotAny(T...values) throws AssertionError { 404 for (var v : values) 405 if (equals(orElse(null), v)) 406 throw error(MSG_unexpectedValueFound, v, value); 407 return returns(); 408 } 409 410 /** 411 * Asserts that the specified object is the same object as this object. 412 * 413 * @param value The value to check against. 414 * @return The fluent return object. 415 * @throws AssertionError If assertion failed. 416 */ 417 public R isSame(T value) throws AssertionError { 418 if (this.value == value) 419 return returns(); 420 throw error(MSG_notTheSameValue, value, Utils2.identity(value), this.value, Utils2.identity(this.value)); 421 } 422 423 /** 424 * Verifies that two objects are equivalent after converting them both to JSON. 425 * 426 * @param o The object to compare against. 427 * @return The fluent return object. 428 * @throws AssertionError If assertion failed. 429 */ 430 public R isSameJsonAs(Object o) throws AssertionError { 431 return isSameSerializedAs(o, JSON); 432 } 433 434 /** 435 * Verifies that two objects are equivalent after converting them both to sorted JSON. 436 * 437 * <p> 438 * Properties, maps, and collections are all sorted on both objects before comparison. 439 * 440 * @param o The object to compare against. 441 * @return The fluent return object. 442 * @throws AssertionError If assertion failed. 443 */ 444 public R isSameSortedJsonAs(Object o) { 445 return isSameSerializedAs(o, JSON_SORTED); 446 } 447 448 /** 449 * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer. 450 * 451 * @param o The object to compare against. 452 * @param serializer The serializer to use to serialize this object. 453 * @return The fluent return object. 454 * @throws AssertionError If assertion failed. 455 */ 456 public R isSameSerializedAs(Object o, WriterSerializer serializer) { 457 var s1 = serializer.toString(value); 458 var s2 = serializer.toString(o); 459 if (Utils.ne(s1, s2)) 460 throw error(MSG_unexpectedComparison, s2, s1); 461 return returns(); 462 } 463 464 /** 465 * Asserts that the object is an instance of the specified class. 466 * 467 * <h5 class='section'>Example:</h5> 468 * <p class='bjava'> 469 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 470 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>); 471 * </p> 472 * 473 * @param parent The value to check against. 474 * @return The fluent return object. 475 * @throws AssertionError If assertion failed. 476 */ 477 public R isType(Class<?> parent) throws AssertionError { 478 Utils.assertArgNotNull("parent", parent); 479 if (! ClassInfo.of(value()).isChildOf(parent)) 480 throw error(MSG_unexpectedType, className(parent), className(value)); 481 return returns(); 482 } 483 484 /** 485 * Asserts that the object is an instance of the specified class. 486 * 487 * <h5 class='section'>Example:</h5> 488 * <p class='bjava'> 489 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 490 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).isExactType(MyBean.<jk>class</jk>); 491 * </p> 492 * 493 * @param type The value to check against. 494 * @return The fluent return object. 495 * @throws AssertionError If assertion failed. 496 */ 497 public R isExactType(Class<?> type) throws AssertionError { 498 Utils.assertArgNotNull("parent", type); 499 if (value().getClass() != type) 500 throw error(MSG_unexpectedType, className(type), className(value)); 501 return returns(); 502 } 503 504 /** 505 * Asserts that the value passes the specified predicate test. 506 * 507 * @param test The predicate to use to test the value. 508 * @return The fluent return object. 509 * @throws AssertionError If assertion failed. 510 */ 511 public R is(Predicate<T> test) throws AssertionError { 512 if (test != null && ! test.test(value)) 513 throw error(getFailureMessage(test, value)); 514 return returns(); 515 } 516 517 /** 518 * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result. 519 * 520 * <h5 class='section'>Example:</h5> 521 * <p class='bjava'> 522 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 523 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 524 * .asJson() 525 * .is(<js>"{foo:'bar',baz:'qux'}"</js>); 526 * </p> 527 * 528 * @param value The expected string value. 529 * @return The fluent return object. 530 */ 531 public R isJson(String value) { 532 return asJson().is(value); 533 } 534 535 //----------------------------------------------------------------------------------------------------------------- 536 // Fluent setters 537 //----------------------------------------------------------------------------------------------------------------- 538 @Override /* Overridden from Assertion */ 539 public FluentObjectAssertion<T,R> setMsg(String msg, Object...args) { 540 super.setMsg(msg, args); 541 return this; 542 } 543 544 @Override /* Overridden from Assertion */ 545 public FluentObjectAssertion<T,R> setOut(PrintStream value) { 546 super.setOut(value); 547 return this; 548 } 549 550 @Override /* Overridden from Assertion */ 551 public FluentObjectAssertion<T,R> setSilent() { 552 super.setSilent(); 553 return this; 554 } 555 556 @Override /* Overridden from Assertion */ 557 public FluentObjectAssertion<T,R> setStdOut() { 558 super.setStdOut(); 559 return this; 560 } 561 562 @Override /* Overridden from Assertion */ 563 public FluentObjectAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) { 564 super.setThrowable(value); 565 return this; 566 } 567 //----------------------------------------------------------------------------------------------------------------- 568 // Utility methods 569 //----------------------------------------------------------------------------------------------------------------- 570 571 /** 572 * Returns the inner value after asserting it is not <jk>null</jk>. 573 * 574 * @return The inner value. 575 * @throws AssertionError If inner value was <jk>null</jk>. 576 */ 577 protected T value() throws AssertionError { 578 isExists(); 579 return value; 580 } 581 582 /** 583 * Returns the inner value as a string. 584 * 585 * @return The inner value as a string, or <jk>null</jk> if the value was null. 586 */ 587 protected String valueAsString() { 588 return Utils.s(value); 589 } 590 591 /** 592 * Returns the inner value or the other value if the value is <jk>null</jk>. 593 * 594 * @param other The other value. 595 * @return The inner value. 596 */ 597 protected T orElse(T other) { 598 return value == null ? other : value; 599 } 600 601 /** 602 * Returns <jk>true</jk> if the inner value is null. 603 * 604 * @return <jk>true</jk> if the inner value is null. 605 */ 606 protected boolean valueIsNull() { 607 return value == null; 608 } 609 610 /** 611 * Returns <jk>true</jk> if the inner value is not null. 612 * 613 * @return <jk>true</jk> if the inner value is not null. 614 */ 615 protected boolean valueIsNotNull() { 616 return value != null; 617 } 618 619 /** 620 * Returns the value wrapped in an {@link Optional}. 621 * 622 * @return The value wrapped in an {@link Optional}. 623 */ 624 protected Optional<T> opt() { 625 return Utils.opt(value); 626 } 627 628 /** 629 * Returns the result of running the specified function against the value and returns the result. 630 * 631 * @param <T2> The mapper-to type. 632 * @param mapper The function to run against the value. 633 * @return The result, never <jk>null</jk>. 634 */ 635 protected <T2> Optional<T2> map(Function<? super T, ? extends T2> mapper) { 636 return opt().map(mapper); 637 } 638 639 /** 640 * Returns the predicate failure message. 641 * 642 * <p> 643 * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}. 644 * Otherwise, returns a generic <js>"Unexpected value: x"</js> message. 645 * 646 * @param p The function to run against the value. 647 * @param value The value that failed the test. 648 * @return The result, never <jk>null</jk>. 649 */ 650 protected String getFailureMessage(Predicate<?> p, Object value) { 651 if (p instanceof AssertionPredicate) 652 return ((AssertionPredicate<?>)p).getFailureMessage(); 653 return format(MSG_unexpectedValue2, value); 654 } 655 656 /** 657 * Checks two objects for equality. 658 * 659 * @param o1 The first object. 660 * @param o2 The second object. 661 * @return <jk>true</jk> if the objects are equal. 662 */ 663 protected boolean equals(Object o1, Object o2) { 664 if (o1 == o2) 665 return true; 666 if (o1 == null || o2 == null) 667 return false; 668 if (o1.equals(o2)) 669 return true; 670 if (isArray(o1)) 671 return stringifyDeep(o1).equals(stringifyDeep(o2)); 672 return false; 673 } 674 675 /** 676 * Returns the string form of the inner object. 677 * Subclasses can override this method to affect the {@link #asString()} method (and related). 678 */ 679 @Override /* Object */ 680 public String toString() { 681 return valueAsString(); 682 } 683}