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 java.util.Collections.*; 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.serializer.*; 031 032/** 033 * Used for fluent assertion calls against throwables. 034 * 035 * <h5 class='section'>Test Methods:</h5> 036 * <p> 037 * <ul class='javatree'> 038 * <li class='jc'>{@link FluentObjectAssertion} 039 * <ul class='javatreec'> 040 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 041 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 042 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 043 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 044 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 045 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 046 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 047 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 048 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 049 * <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)} 050 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 051 * <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)} 052 * <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)} 053 * <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)} 054 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 055 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 056 * </ul> 057 * </ul> 058 * 059 * <h5 class='section'>Transform Methods:</h5> 060 * <p> 061 * <ul class='javatree'> 062 * <li class='jc'>{@link FluentThrowableAssertion} 063 * <ul class='javatreec'> 064 * <li class='jm'>{@link FluentThrowableAssertion#asMessage() asMessage()} 065 * <li class='jm'>{@link FluentThrowableAssertion#asMessages() asMessages()} 066 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessage() asLocalizedMessage()} 067 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessages() asLocalizedMessages()} 068 * <li class='jm'>{@link FluentThrowableAssertion#asStackTrace() asStackTrace()} 069 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy() asCausedBy()} 070 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy(Class) asCausedBy(Class)} 071 * <li class='jm'>{@link FluentThrowableAssertion#asFind(Class) asFind(Class)} 072 * </ul> 073 * <li class='jc'>{@link FluentObjectAssertion} 074 * <ul class='javatreec'> 075 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 076 * <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)} 077 * <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)} 078 * <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()} 079 * <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()} 080 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)} 081 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 082 * </ul> 083 * </ul> 084 * 085 * <h5 class='section'>Configuration Methods:</h5> 086 * <p> 087 * <ul class='javatree'> 088 * <li class='jc'>{@link Assertion} 089 * <ul class='javatreec'> 090 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 091 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 092 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 093 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 094 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 095* </ul> 096 * </ul> 097 * 098 * <h5 class='section'>See Also:</h5><ul> 099 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a> 100 * </ul> 101 * 102 * @param <T> The throwable type. 103 * @param <R> The return type. 104 */ 105public class FluentThrowableAssertion<T extends Throwable,R> extends FluentObjectAssertion<T,R> { 106 107 //----------------------------------------------------------------------------------------------------------------- 108 // Static 109 //----------------------------------------------------------------------------------------------------------------- 110 111 private static final Messages MESSAGES = Messages.of(FluentThrowableAssertion.class, "Messages"); 112 private static final String 113 MSG_exceptionWasNotExpectedType = MESSAGES.getString("exceptionWasNotExpectedType"), 114 MSG_exceptionWasNotThrown = MESSAGES.getString("exceptionWasNotThrown"), 115 MSG_causedByExceptionNotExpectedType = MESSAGES.getString("causedByExceptionNotExpectedType"); 116 117 //----------------------------------------------------------------------------------------------------------------- 118 // Instance 119 //----------------------------------------------------------------------------------------------------------------- 120 121 /** 122 * Constructor. 123 * 124 * @param value 125 * The object being tested. 126 * <br>Can be <jk>null</jk>. 127 * @param returns 128 * The object to return after a test method is called. 129 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 130 * used on the same assertion. 131 */ 132 public FluentThrowableAssertion(T value, R returns) { 133 this(null, value, returns); 134 } 135 136 /** 137 * Chained constructor. 138 * 139 * <p> 140 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 141 * 142 * @param creator 143 * The assertion that created this assertion. 144 * <br>Should be <jk>null</jk> if this is the top-level assertion. 145 * @param value 146 * The object being tested. 147 * <br>Can be <jk>null</jk>. 148 * @param returns 149 * The object to return after a test method is called. 150 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 151 * used on the same assertion. 152 */ 153 public FluentThrowableAssertion(Assertion creator, T value, R returns) { 154 super(creator, value, returns); 155 } 156 157 //----------------------------------------------------------------------------------------------------------------- 158 // Transform methods 159 //----------------------------------------------------------------------------------------------------------------- 160 161 @Override /* FluentObjectAssertion */ 162 public FluentThrowableAssertion<T,R> asTransformed(Function<T,T> function) { // NOSONAR - Intentional. 163 return new FluentThrowableAssertion<>(this, function.apply(orElse(null)), returns()); 164 } 165 166 /** 167 * Returns an assertion against the throwable message. 168 * 169 * <h5 class='section'>Example:</h5> 170 * <p class='bjava'> 171 * <jc>// Asserts that the specified method throws an exception 172 * // with 'foobar' somewhere in the messages. </jc> 173 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 174 * .asMessage() 175 * .isPattern(<js>".*foobar.*"</js>); 176 * </p> 177 * 178 * @return An assertion against the throwable message. Never <jk>null</jk>. 179 */ 180 public FluentStringAssertion<R> asMessage() { 181 return new FluentStringAssertion<>(this, map(Throwable::getMessage).orElse(null), returns()); 182 } 183 184 /** 185 * Returns an assertion against the throwable message and all caused-by messages. 186 * 187 * <h5 class='section'>Example:</h5> 188 * <p class='bjava'> 189 * <jc>// Asserts that the specified method throws an exception with 190 * // 'foobar' somewhere in the messages. </jc> 191 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 192 * .asMessages() 193 * .isPattern(<js>".*foobar.*"</js>); 194 * </p> 195 * 196 * @return An assertion against the throwable message. Never <jk>null</jk>. 197 */ 198 public FluentListAssertion<String,R> asMessages() { 199 List<String> l = null; 200 Throwable t = orElse(null); 201 if (t != null) { 202 if (t.getCause() == null) 203 l = singletonList(t.getMessage()); 204 else { 205 l = list(); 206 while (t != null) { 207 l.add(t.getMessage()); 208 t = t.getCause(); 209 } 210 } 211 } 212 return new FluentListAssertion<>(this, l, returns()); 213 } 214 215 /** 216 * Returns an assertion against the throwable localized message. 217 * 218 * <h5 class='section'>Example:</h5> 219 * <p class='bjava'> 220 * <jc>// Asserts that the specified method throws an exception with 221 * // 'foobar' somewhere in the localized messages. </jc> 222 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 223 * .asLocalizedMessage() 224 * .isPattern(<js>".*foobar.*"</js>); 225 * </p> 226 * 227 * @return An assertion against the throwable localized message. Never <jk>null</jk>. 228 */ 229 public FluentStringAssertion<R> asLocalizedMessage() { 230 return new FluentStringAssertion<>(this, map(Throwable::getLocalizedMessage).orElse(null), returns()); 231 } 232 233 /** 234 * Returns an assertion against the throwable message and all caused-by messages. 235 * 236 * <h5 class='section'>Example:</h5> 237 * <p class='bjava'> 238 * <jc>// Asserts that the specified method throws an exception with 239 * // 'foobar' somewhere in the messages. </jc> 240 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 241 * .asLocalizedMessages() 242 * .isPattern(<js>".*foobar.*"</js>); 243 * </p> 244 * 245 * @return An assertion against the throwable message. Never <jk>null</jk>. 246 */ 247 public FluentListAssertion<String,R> asLocalizedMessages() { 248 List<String> l = null; 249 Throwable t = orElse(null); 250 if (t != null) { 251 if (t.getCause() == null) 252 l = singletonList(t.getMessage()); 253 else { 254 l = list(); 255 while (t != null) { 256 l.add(t.getLocalizedMessage()); 257 t = t.getCause(); 258 } 259 } 260 } 261 return new FluentListAssertion<>(this, l, returns()); 262 } 263 264 /** 265 * Returns an assertion against the throwable localized message. 266 * 267 * <h5 class='section'>Example:</h5> 268 * <p class='bjava'> 269 * <jc>// Asserts that the specified method throws an exception with 270 * // 'foobar' somewhere in the stack trace. </jc> 271 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 272 * .asStackTrace() 273 * .isPattern(<js>"foobar"</js>); 274 * </p> 275 * 276 * @return An assertion against the throwable stacktrace. Never <jk>null</jk>. 277 */ 278 public FluentStringListAssertion<R> asStackTrace() { 279 return new FluentStringListAssertion<>(this, valueIsNull() ? null : Arrays.asList(getStackTrace(value())), returns()); 280 } 281 282 /** 283 * Returns an assertion against the caused-by throwable. 284 * 285 * <h5 class='section'>Example:</h5> 286 * <p class='bjava'> 287 * <jc>// Asserts that the specified method throws an exception whose 288 * // caused-by message contains 'foobar'. </jc> 289 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 290 * .asCausedBy() 291 * .asMessage() 292 * .isPattern(<js>"foobar"</js>); 293 * </p> 294 * 295 * @return An assertion against the caused-by. Never <jk>null</jk>. 296 */ 297 public FluentThrowableAssertion<Throwable,R> asCausedBy() { 298 return asCausedBy(Throwable.class); 299 } 300 301 /** 302 * Returns an assertion against the caused-by throwable. 303 * 304 * <h5 class='section'>Example:</h5> 305 * <p class='bjava'> 306 * <jc>// Asserts that the specified method throws an exception whose 307 * // caused-by message contains 'foobar'. </jc> 308 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 309 * .asCausedBy(RuntimeException.<jk>class</jk>) 310 * .asMessage() 311 * .isPattern(<js>"foobar"</js>); 312 * </p> 313 * 314 * @param <X> The throwable type. 315 * @param type The expected exception type. 316 * @return An assertion against the caused-by. Never <jk>null</jk>. 317 */ 318 public <X extends Throwable> FluentThrowableAssertion<X,R> asCausedBy(Class<X> type) { 319 var t = map(Throwable::getCause).orElse(null); 320 if (t == null || type.isInstance(t)) 321 return new FluentThrowableAssertion<>(this, type.cast(t), returns()); 322 throw error(MSG_causedByExceptionNotExpectedType, type, t.getClass()); 323 } 324 325 /** 326 * Returns an assertion against the throwable localized message. 327 * 328 * <h5 class='section'>Example:</h5> 329 * <p class='bjava'> 330 * <jc>// Asserts that the specified method throws an exception with a 331 * // caused-by RuntimeException containing 'foobar'</jc> 332 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 333 * .findCausedBy(RuntimeException.<jk>class</jk>) 334 * .isExists() 335 * .asMessage() 336 * .isPattern(<js>"foobar"</js>); 337 * </p> 338 * 339 * @param <X> The throwable type. 340 * @param throwableClass The class type to search for in the caused-by chain. 341 * @return An assertion against the caused-by throwable. Never <jk>null</jk>. 342 */ 343 public <X extends Throwable> FluentThrowableAssertion<X,R> asFind(Class<X> throwableClass) { 344 Throwable t = orElse(null); 345 while (t != null) { 346 if (throwableClass.isInstance(t)) 347 return new FluentThrowableAssertion<>(this, throwableClass.cast(t), returns()); 348 t = t.getCause(); 349 } 350 return new FluentThrowableAssertion<>(this, (X)null, returns()); 351 } 352 353 //----------------------------------------------------------------------------------------------------------------- 354 // Test methods 355 //----------------------------------------------------------------------------------------------------------------- 356 357 /** 358 * Asserts that this throwable is of the specified type. 359 * 360 * <h5 class='section'>Example:</h5> 361 * <p class='bjava'> 362 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 363 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 364 * .isType(RuntimeException.<jk>class</jk>); 365 * </p> 366 * 367 * @param parent The type. 368 * @return The fluent return object. 369 */ 370 @Override 371 public R isType(Class<?> parent) { 372 Utils.assertArgNotNull("parent", parent); 373 if (! parent.isInstance(value())) 374 throw error(MSG_exceptionWasNotExpectedType, className(parent), className(value())); 375 return returns(); 376 } 377 378 /** 379 * Asserts that this throwable is exactly the specified type. 380 * 381 * <h5 class='section'>Example:</h5> 382 * <p class='bjava'> 383 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 384 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 385 * .isExactType(RuntimeException.<jk>class</jk>); 386 * </p> 387 * 388 * @param type The type. 389 * @return The fluent return object. 390 */ 391 @Override 392 public R isExactType(Class<?> type) { 393 Utils.assertArgNotNull("type", type); 394 if (type != value().getClass()) 395 throw error(MSG_exceptionWasNotExpectedType, className(type), className(value())); 396 return returns(); 397 } 398 399 /** 400 * Asserts that this throwable exists. 401 * 402 * <h5 class='section'>Example:</h5> 403 * <p class='bjava'> 404 * <jc>// Asserts that the specified method throws any exception.</jc> 405 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()).isExists(); 406 * </p> 407 * 408 * @return The fluent return object. 409 */ 410 @Override 411 public R isExists() { 412 if (valueIsNull()) 413 throw error(MSG_exceptionWasNotThrown); 414 return returns(); 415 } 416 417 //----------------------------------------------------------------------------------------------------------------- 418 // Fluent setters 419 //----------------------------------------------------------------------------------------------------------------- 420 @Override /* Overridden from Assertion */ 421 public FluentThrowableAssertion<T,R> setMsg(String msg, Object...args) { 422 super.setMsg(msg, args); 423 return this; 424 } 425 426 @Override /* Overridden from Assertion */ 427 public FluentThrowableAssertion<T,R> setOut(PrintStream value) { 428 super.setOut(value); 429 return this; 430 } 431 432 @Override /* Overridden from Assertion */ 433 public FluentThrowableAssertion<T,R> setSilent() { 434 super.setSilent(); 435 return this; 436 } 437 438 @Override /* Overridden from Assertion */ 439 public FluentThrowableAssertion<T,R> setStdOut() { 440 super.setStdOut(); 441 return this; 442 } 443 444 @Override /* Overridden from Assertion */ 445 public FluentThrowableAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) { 446 super.setThrowable(value); 447 return this; 448 } 449 //----------------------------------------------------------------------------------------------------------------- 450 // Utility methods 451 //----------------------------------------------------------------------------------------------------------------- 452 453 @Override 454 protected boolean equals(Object o1, Object o2) { 455 if (o1 instanceof Throwable o1t && o2 instanceof Throwable o2t) 456 return Utils.eq(o1t, o2t, (x,y)->Utils.eq(x.getClass(),y.getClass()) && Utils.eq(x.getMessage(),y.getMessage())); 457 return super.equals(o1, o2); 458 } 459}