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