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.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 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.cp.*; 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 // @formatter:off 108 private static final Messages MESSAGES = Messages.of(FluentThrowableAssertion.class, "Messages"); 109 private static final String 110 MSG_exceptionWasNotExpectedType = MESSAGES.getString("exceptionWasNotExpectedType"), 111 MSG_exceptionWasNotThrown = MESSAGES.getString("exceptionWasNotThrown"), 112 MSG_causedByExceptionNotExpectedType = MESSAGES.getString("causedByExceptionNotExpectedType"); 113 // @formatter:on 114 115 /** 116 * Chained constructor. 117 * 118 * <p> 119 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 120 * 121 * @param creator 122 * The assertion that created this assertion. 123 * <br>Should be <jk>null</jk> if this is the top-level assertion. 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(Assertion creator, T value, R returns) { 133 super(creator, value, returns); 134 } 135 136 /** 137 * Constructor. 138 * 139 * @param value 140 * The object being tested. 141 * <br>Can be <jk>null</jk>. 142 * @param returns 143 * The object to return after a test method is called. 144 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 145 * used on the same assertion. 146 */ 147 public FluentThrowableAssertion(T value, R returns) { 148 this(null, value, returns); 149 } 150 151 /** 152 * Returns an assertion against the caused-by throwable. 153 * 154 * <h5 class='section'>Example:</h5> 155 * <p class='bjava'> 156 * <jc>// Asserts that the specified method throws an exception whose 157 * // caused-by message contains 'foobar'. </jc> 158 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 159 * .asCausedBy() 160 * .asMessage() 161 * .isPattern(<js>"foobar"</js>); 162 * </p> 163 * 164 * @return An assertion against the caused-by. Never <jk>null</jk>. 165 */ 166 public FluentThrowableAssertion<Throwable,R> asCausedBy() { 167 return asCausedBy(Throwable.class); 168 } 169 170 /** 171 * Returns an assertion against the caused-by throwable. 172 * 173 * <h5 class='section'>Example:</h5> 174 * <p class='bjava'> 175 * <jc>// Asserts that the specified method throws an exception whose 176 * // caused-by message contains 'foobar'. </jc> 177 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 178 * .asCausedBy(RuntimeException.<jk>class</jk>) 179 * .asMessage() 180 * .isPattern(<js>"foobar"</js>); 181 * </p> 182 * 183 * @param <X> The throwable type. 184 * @param type The expected exception type. 185 * @return An assertion against the caused-by. Never <jk>null</jk>. 186 */ 187 public <X extends Throwable> FluentThrowableAssertion<X,R> asCausedBy(Class<X> type) { 188 var t = map(Throwable::getCause).orElse(null); 189 if (t == null || type.isInstance(t)) 190 return new FluentThrowableAssertion<>(this, type.cast(t), returns()); 191 throw error(MSG_causedByExceptionNotExpectedType, cn(type), cn(t.getClass())); 192 } 193 194 /** 195 * Returns an assertion against the throwable localized message. 196 * 197 * <h5 class='section'>Example:</h5> 198 * <p class='bjava'> 199 * <jc>// Asserts that the specified method throws an exception with a 200 * // caused-by RuntimeException containing 'foobar'</jc> 201 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 202 * .findCausedBy(RuntimeException.<jk>class</jk>) 203 * .isExists() 204 * .asMessage() 205 * .isPattern(<js>"foobar"</js>); 206 * </p> 207 * 208 * @param <X> The throwable type. 209 * @param throwableClass The class type to search for in the caused-by chain. 210 * @return An assertion against the caused-by throwable. Never <jk>null</jk>. 211 */ 212 public <X extends Throwable> FluentThrowableAssertion<X,R> asFind(Class<X> throwableClass) { 213 Throwable t = orElse(null); 214 while (nn(t)) { 215 if (throwableClass.isInstance(t)) 216 return new FluentThrowableAssertion<>(this, throwableClass.cast(t), returns()); 217 t = t.getCause(); 218 } 219 return new FluentThrowableAssertion<>(this, (X)null, returns()); 220 } 221 222 /** 223 * Returns an assertion against the throwable localized message. 224 * 225 * <h5 class='section'>Example:</h5> 226 * <p class='bjava'> 227 * <jc>// Asserts that the specified method throws an exception with 228 * // 'foobar' somewhere in the localized messages. </jc> 229 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 230 * .asLocalizedMessage() 231 * .isPattern(<js>".*foobar.*"</js>); 232 * </p> 233 * 234 * @return An assertion against the throwable localized message. Never <jk>null</jk>. 235 */ 236 public FluentStringAssertion<R> asLocalizedMessage() { 237 return new FluentStringAssertion<>(this, map(Throwable::getLocalizedMessage).orElse(null), returns()); 238 } 239 240 /** 241 * Returns an assertion against the throwable message and all caused-by messages. 242 * 243 * <h5 class='section'>Example:</h5> 244 * <p class='bjava'> 245 * <jc>// Asserts that the specified method throws an exception with 246 * // 'foobar' somewhere in the messages. </jc> 247 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 248 * .asLocalizedMessages() 249 * .isPattern(<js>".*foobar.*"</js>); 250 * </p> 251 * 252 * @return An assertion against the throwable message. Never <jk>null</jk>. 253 */ 254 public FluentListAssertion<String,R> asLocalizedMessages() { 255 var l = (List<String>)null; 256 Throwable t = orElse(null); 257 if (nn(t)) { 258 if (t.getCause() == null) 259 l = singletonList(t.getMessage()); 260 else { 261 l = list(); 262 while (nn(t)) { 263 l.add(lm(t)); 264 t = t.getCause(); 265 } 266 } 267 } 268 return new FluentListAssertion<>(this, l, returns()); 269 } 270 271 /** 272 * Returns an assertion against the throwable message. 273 * 274 * <h5 class='section'>Example:</h5> 275 * <p class='bjava'> 276 * <jc>// Asserts that the specified method throws an exception 277 * // with 'foobar' somewhere in the messages. </jc> 278 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 279 * .asMessage() 280 * .isPattern(<js>".*foobar.*"</js>); 281 * </p> 282 * 283 * @return An assertion against the throwable message. Never <jk>null</jk>. 284 */ 285 public FluentStringAssertion<R> asMessage() { 286 return new FluentStringAssertion<>(this, map(Throwable::getMessage).orElse(null), returns()); 287 } 288 289 /** 290 * Returns an assertion against the throwable message and all caused-by messages. 291 * 292 * <h5 class='section'>Example:</h5> 293 * <p class='bjava'> 294 * <jc>// Asserts that the specified method throws an exception with 295 * // 'foobar' somewhere in the messages. </jc> 296 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 297 * .asMessages() 298 * .isPattern(<js>".*foobar.*"</js>); 299 * </p> 300 * 301 * @return An assertion against the throwable message. Never <jk>null</jk>. 302 */ 303 public FluentListAssertion<String,R> asMessages() { 304 var l = (List<String>)null; 305 Throwable t = orElse(null); 306 if (nn(t)) { 307 if (t.getCause() == null) 308 l = singletonList(t.getMessage()); 309 else { 310 l = list(); 311 while (nn(t)) { 312 l.add(t.getMessage()); 313 t = t.getCause(); 314 } 315 } 316 } 317 return new FluentListAssertion<>(this, l, returns()); 318 } 319 320 /** 321 * Returns an assertion against the throwable localized message. 322 * 323 * <h5 class='section'>Example:</h5> 324 * <p class='bjava'> 325 * <jc>// Asserts that the specified method throws an exception with 326 * // 'foobar' somewhere in the stack trace. </jc> 327 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 328 * .asStackTrace() 329 * .isPattern(<js>"foobar"</js>); 330 * </p> 331 * 332 * @return An assertion against the throwable stacktrace. Never <jk>null</jk>. 333 */ 334 public FluentStringListAssertion<R> asStackTrace() { 335 return new FluentStringListAssertion<>(this, valueIsNull() ? null : l(getStackTrace(value())), returns()); 336 } 337 338 @Override /* Overridden from FluentObjectAssertion */ 339 public FluentThrowableAssertion<T,R> asTransformed(Function<T,T> function) { // NOSONAR - Intentional. 340 return new FluentThrowableAssertion<>(this, function.apply(orElse(null)), returns()); 341 } 342 343 /** 344 * Asserts that this throwable is exactly the specified type. 345 * 346 * <h5 class='section'>Example:</h5> 347 * <p class='bjava'> 348 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 349 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 350 * .isExactType(RuntimeException.<jk>class</jk>); 351 * </p> 352 * 353 * @param type The type. 354 * @return The fluent return object. 355 */ 356 @Override 357 public R isExactType(Class<?> type) { 358 assertArgNotNull("type", type); 359 if (type != value().getClass()) 360 throw error(MSG_exceptionWasNotExpectedType, cn(type), cn(value())); 361 return returns(); 362 } 363 364 /** 365 * Asserts that this throwable exists. 366 * 367 * <h5 class='section'>Example:</h5> 368 * <p class='bjava'> 369 * <jc>// Asserts that the specified method throws any exception.</jc> 370 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()).isExists(); 371 * </p> 372 * 373 * @return The fluent return object. 374 */ 375 @Override 376 public R isExists() { 377 if (valueIsNull()) 378 throw error(MSG_exceptionWasNotThrown); 379 return returns(); 380 } 381 382 /** 383 * Asserts that this throwable is of the specified type. 384 * 385 * <h5 class='section'>Example:</h5> 386 * <p class='bjava'> 387 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 388 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 389 * .isType(RuntimeException.<jk>class</jk>); 390 * </p> 391 * 392 * @param parent The type. 393 * @return The fluent return object. 394 */ 395 @Override 396 public R isType(Class<?> parent) { 397 assertArgNotNull("parent", parent); 398 if (! parent.isInstance(value())) 399 throw error(MSG_exceptionWasNotExpectedType, cn(parent), cn(value())); 400 return returns(); 401 } 402 403 @Override /* Overridden from Assertion */ 404 public FluentThrowableAssertion<T,R> setMsg(String msg, Object...args) { 405 super.setMsg(msg, args); 406 return this; 407 } 408 409 @Override /* Overridden from Assertion */ 410 public FluentThrowableAssertion<T,R> setOut(PrintStream value) { 411 super.setOut(value); 412 return this; 413 } 414 415 @Override /* Overridden from Assertion */ 416 public FluentThrowableAssertion<T,R> setSilent() { 417 super.setSilent(); 418 return this; 419 } 420 421 @Override /* Overridden from Assertion */ 422 public FluentThrowableAssertion<T,R> setStdOut() { 423 super.setStdOut(); 424 return this; 425 } 426 427 @Override /* Overridden from Assertion */ 428 public FluentThrowableAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) { 429 super.setThrowable(value); 430 return this; 431 } 432 433 @Override 434 protected boolean equals(Object o1, Object o2) { 435 if (o1 instanceof Throwable o1t && o2 instanceof Throwable o2t) 436 return eq(o1t, o2t, (x, y) -> eq(x.getClass(), y.getClass()) && eq(x.getMessage(), y.getMessage())); 437 return super.equals(o1, o2); 438 } 439}