![JAR search and dependency download from the Maven repository](/logo.png)
com.googlecode.catchexception.CatchException Maven / Gradle / Ivy
/*
* Copyright 2011-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.catchexception;
import com.googlecode.catchexception.apis.BDDCatchException;
import com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers;
/**
* The methods of this class catch and verify exceptions in a single line of code and make them available for
* further analysis. This Javadoc content is also available on the
* catch-exception web page. Documentation
* 1. How to use catch-exception? 2. What is this stuff actually good for?
* 3. How does it work internally? 4. When is the caught exception reset?
* 5. My code throws a ClassCastException. Why? 6. The exception is not caught. Why?
* 7. Do I have to care about memory leaks? 8. The caught exception is not available in
* another thread. Why? 9. How do I catch an exception thrown by a static method? 11.
* Can I catch errors instead of exceptions? 1. How to use catch-exception? The most basic
* usage is: import static com.googlecode.catchexception.CatchException.*;
*
* // call customerService.prepareBilling(Prize.Zero)
* // and catch the exception if any is thrown
* catchException(customerService).prepareBilling(Prize.Zero);
*
* // assert that an IllegalArgumentException was thrown
* assert caughtException() instanceof IllegalArgumentException;
You can combine the two lines of code in a
* single one if you like: // call customerService.prepareBilling(Prize.Zero)
* // and throw an ExceptionNotThrownAssertionError if
* // the expected exception is not thrown
* verifyException(customerService, IllegalArgumentException.class).prepareBilling(Prize.Zero);
There is a minor
* difference between both variants. In the first variant you must start the JVM with option -ea
to enable
* the assertion. The second variant does not use JDK assertions and ,therefore, always verifies the caught exception. A
* third variant allows you to select the type of exceptions you want to catch (no verification involved):
* // catch IllegalArgumentExceptions but no other exceptions
* catchException(customerService, IllegalArgumentException.class).prepareBilling(Prize.Zero);
The fourth and
* last variant verifies that some exception is thrown, i.e. the type of the exception does not matter:
* verifyException(customerService).prepareBilling(Prize.Zero);
In all variants you can use
* caughtException()
afterwards to inspect the caught exception. Finally, there some alternative ways to
* catch and verify exceptions:
*
* - {@link BDDCatchException} - a BDD-like approach,
*
- {@link CatchExceptionHamcrestMatchers} - Hamcrest assertions
*
* 2. What is this stuff actually good for? This class targets concise and robust code in tests. Dadid
* Saff, a commiter to JUnit, has
* discussed this approach in 2007.
* Let me summarize the arguments here. There are two advantages of the approach proposed here in comparison to the use
* of try/catch blocks.
*
* - The test is more concise and easier to read.
*
- The test cannot be corrupted by a missing assertion. Assume you forgot to type
fail()
behind the
* method call that is expected to throw an exception.
*
* There are also some advantages of this approach in comparison to test runner-specific mechanisms that catch and
* verify exceptions.
*
* - A single test can verify more than one thrown exception.
*
- The test can verify the properties of the thrown exception after the exception is caught.
*
- The test can specify by which method call the exception must be thrown.
*
- The test does not depend on a specific test runner (JUnit4, TestNG).
*
* 3. How does it work internally? The method catchException(obj)
wraps the given object
* with a proxy that catches the exception, then (optionally) verifies the exception, and finally attaches the exception
* to the current thread for further analysis. The known limitations for
* proxies apply. Is both memory consumption and runtime a concern for you? Then use try/catch blocks instead of this
* class. Because in this case the creation of proxies is an unnecessary overhead. If only either memory consumption or
* runtime is an issue for you, feel free to configure the cache of the underlying proxy factories as appropriate.
* 4. When is the caught exception reset? The Method {@link #caughtException()} returns the exception
* thrown by the last method call on a proxied object in the current thread, i.e. it is reset by calling a method on the
* proxied object. If the called method has not thrown an exception, caughtException()
returns null. To
* reset the caught exception manually, call {@link #resetCaughtException()}. At the moment there is no way to reset
* exceptions that have been caught in other threads. 5. My code throws a ClassCastException. Why?
* Example: StringBuilder sb = new StringBuilder();
* catchException(sb).charAt(-2); // throws ClassCastException
Probably you have tested a final class. Proxy
* factories usually try to subclass the type of the proxied object. This is not possible if the original class is
* final. But there is a way out. If the tested method belongs to an interface, then you can cast the argument (here:
* sb
) to that interface or ,easier, change the declared type of the argument to the interface type. This
* works because the created proxy is not longer required to have the same type as the original class but it must only
* have the same interface. // first variant
* StringBuilder sb = new StringBuilder();
* catchException((CharSequence) sb).charAt(-2); // works fine
*
* // second variant
* CharSequence sb = new StringBuilder();
* catchException(sb).charAt(-2); // works fine
If the tested method does no belong to an interface fall back to
* the try/catch-blocks or use Powermock .
* // example for
* PowerMock with JUnit4
* @RunWith(PowerMockRunner.class)
* @PrepareForTest({ MyFinalType.class })
* public class MyTest {
*
6. Do I have to care about memory leaks? This library uses a {@link ThreadLocal}.
* ThreadLocals are known to cause memory leaks if they refer to a class the garbage collector would like to collect. If
* you use this library only for testing, then memory leaks do not worry you. If you use this library for other purposes
* than testing, you should care. 7. The caught exception is not available in another thread. Why? The
* caught exception is saved at the thread the exception is thrown in. This is the reason the
* exception is not visible within any other thread. 8. Is there a way to get rid of the throws clause in my
* test method? Example: public void testSomething() throws Exception {
* ...
* catchException(obj).do(); // do() throws a checked exception
No, although the exception is always caught you
* cannot omit the throws clause in your test method. 11. Can I catch errors instead of exceptions?
* Yes, have a look at {@code com.googlecode.catchexception.throwable.CatchThrowable} (in module catch-throwable).
*/
public final class CatchException {
/**
* Prevents Instantiation of a new catch exception.
*/
private CatchException() {
// Prevent instantiation
}
/**
* Returns the exception caught during the last call in the current thread.
*
* @param
* This type parameter makes some type casts redundant.
*
* @return Returns the exception caught during the last call in the current thread - if the call was made through a
* proxy that has been created via {@link #verifyException(ThrowingCallable, Class) verifyException()} or
* {@link #catchException(ThrowingCallable, Class) catchException()}. Returns null when no exception was
* caught.
*/
public static E caughtException() {
return ExceptionHolder.get();
}
/**
* Caught exception.
*
* @param
* the element type
* @param caughtExceptionType
* the caught exception type
*
* @return the e
*
* @deprecated Use caughtXception() as passed argument is not used
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public static E caughtException(Class caughtExceptionType) {
return ExceptionHolder.get();
}
/**
* Use it to verify that an exception is thrown and to get access to the thrown exception (for further
* verifications). The following example verifies that obj.doX() throws a Exception:
* verifyException(obj).doX(); // catch and verify
* assert "foobar".equals(caughtException().getMessage()); // further analysis
*
If doX()
does not throw a Exception
, then a
* {@link ExceptionNotThrownAssertionError} is thrown. Otherwise the thrown exception can be retrieved via
* {@link #caughtException()}.
*
* @param actor
* The instance that shall be proxied. Must not be null
.
*/
public static void verifyException(ThrowingCallable actor) {
verifyException(actor, Exception.class);
}
/**
* Use it to verify that an exception of specific type is thrown and to get access to the thrown exception (for
* further verifications). The following example verifies that obj.doX() throws a MyException:
* verifyException(obj, MyException.class).doX(); // catch and verify
* assert "foobar".equals(caughtException().getMessage()); // further analysis
*
If doX()
does not throw a MyException
, then a
* {@link ExceptionNotThrownAssertionError} is thrown. Otherwise the thrown exception can be retrieved via
* {@link #caughtException()}.
*
* @param actor
* The instance that shall be proxied. Must not be null
.
* @param clazz
* The type of the exception that shall be thrown by the underlying object. Must not be
* null
.
*/
public static void verifyException(ThrowingCallable actor, Class extends Exception> clazz) {
validateArguments(actor, clazz);
catchException(actor, clazz, true);
}
/**
* Use it to catch an exception and to get access to the thrown exception (for further verifications). In the
* following example you catch exceptions that are thrown by obj.doX(): catchException(obj).doX(); // catch
* if (caughtException() != null) {
* assert "foobar".equals(caughtException().getMessage()); // further analysis
* }
If doX()
throws a exception, then {@link #caughtException()} will return the caught
* exception. If doX()
does not throw a exception, then {@link #caughtException()} will return
* null
.
*
* @param actor
* The instance that shall be proxied. Must not be null
.
*/
public static void catchException(com.googlecode.catchexception.ThrowingCallable actor) {
validateArguments(actor, Exception.class);
catchException(actor, Exception.class, false);
}
/**
* Use it to catch an exception of a specific type and to get access to the thrown exception (for further
* verifications). In the following example you catch exceptions of type MyException that are thrown by obj.doX():
* catchException(obj, MyException.class).doX(); // catch
* if (caughtException() != null) {
* assert "foobar".equals(caughtException().getMessage()); // further analysis
* }
If doX()
throws a MyException
, then {@link #caughtException()} will return the
* caught exception. If doX()
does not throw a MyException
, then
* {@link #caughtException()} will return null
. If doX()
throws an exception of another
* type, i.e. not a subclass but another class, then this exception is not thrown and {@link #caughtException()}
* will return null
.
*
* @param actor
* The instance that shall be proxied. Must not be null
.
* @param clazz
* The type of the exception that shall be caught. Must not be null
.
*/
public static void catchException(ThrowingCallable actor, Class extends Exception> clazz) {
validateArguments(actor, clazz);
catchException(actor, clazz, false);
}
/**
* Catch exception.
*
* @param actor
* the actor
* @param clazz
* the clazz
* @param assertException
* the assert exception
*/
private static void catchException(ThrowingCallable actor, Class extends Exception> clazz,
boolean assertException) {
resetCaughtException();
var exception = ExceptionCaptor.captureThrowable(actor);
if (exception == null) {
if (!assertException) {
return;
}
throw new ExceptionNotThrownAssertionError(clazz);
}
// is the thrown exception of the expected type?
if (clazz.isAssignableFrom(exception.getClass())) {
ExceptionHolder.set(exception);
} else if (assertException) {
throw new ExceptionNotThrownAssertionError(clazz, exception);
} else {
ExceptionUtil.sneakyThrow(exception);
}
}
/**
* Validate arguments.
*
* @param actor
* the actor
* @param clazz
* the clazz
*/
private static void validateArguments(ThrowingCallable actor, Class extends Exception> clazz) {
if (actor == null) {
throw new IllegalArgumentException("obj must not be null");
}
if (clazz == null) {
throw new IllegalArgumentException("exceptionClazz must not be null");
}
}
/**
* Sets the {@link #caughtException() caught exception} to null. This does not affect exceptions saved at threads
* other than the current one. Actually you probably never need to call this method because each method call on a
* proxied object in the current thread resets the caught exception. But if you want to improve test isolation or if
* you want to 'clean up' after testing (to avoid memory leaks), call the method before or after testing.
*/
public static void resetCaughtException() {
ExceptionHolder.set(null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy