package org.marketcetera.module;
import org.marketcetera.util.misc.ClassVersion;
import org.marketcetera.util.log.I18NMessage;
import org.marketcetera.util.log.I18NBoundMessage;
import org.marketcetera.util.except.I18NException;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertNotNull;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.io.Serializable;
/* $License$ */
/**
* A utility class for testing failure conditions in unit tests.
*
* <p>
* The code that is being tested is implemented within the
* {@link #run()} method.
*<p>
* The type parameter <code>T</code> reflects the actual type
* of exception that is expected to be thrown from the {@link #run()}
* method.
*<p>
* The class can be used to test failures as follows.
*
* <pre>
* // Testing failures that throw I18NException subclass
* new ExpectedFailure<I18NExceptionSubClass>(Messages.FAILURE_MESSAGE,parameter1) {
* protected void run() throws Exception {
* // The code that is expected to throw I18NExceptionSubClass
* // with <code>Messages.FAILURE_MESSAGE</code> message that has a
* // parameter <code>parameter1</code>
* }
* }
*
*
* // Testing failures that throw any java Exception subclass
* new ExpectedFailure<ExceptionSubClass>(Messages.FAILURE_MESSAGE.getText()) {
* protected void run() throws Exception {
* // The code that is expected to throw ExceptionSubClass
* // with <code>Messages.FAILURE_MESSAGE</code> message
* }
* }
* </pre>
*
* If the caller needs to do further verification of the exception
* than is done by this class, they can get the caught exception by
* calling {@link #getException()}
*
* @author anshul@marketcetera.com
*/
@ClassVersion("$Id: ExpectedFailure.java 16154 2012-07-14 16:34:05Z colin $")
public abstract class ExpectedFailure<T extends Exception> {
/**
* Returns the exception instance, for further verification, if needed.
*
* @return the exception instance.
*/
public T getException() {
return mException;
}
/**
* Verifies if the supplied exception is an I18NException having
* the supplied message and expected parameters.
*
* @param inThrowable the exception to verify.
* @param inExpectedMessage the expected message, ignored if null.
* @param inExpectedParams the expected parameters, ignored if null.
*
* @return the supplied inThrowable value.
*/
public static I18NException assertI18NException(Throwable inThrowable,
I18NMessage inExpectedMessage,
Object... inExpectedParams) {
assertNotNull(inThrowable);
assertTrue(inThrowable.getClass().toString(),
inThrowable instanceof I18NException);
I18NException e = (I18NException) inThrowable;
if(inExpectedMessage != null) {
assertEquals(inThrowable.toString(), inExpectedMessage,
e.getI18NBoundMessage().getMessage());
}
if(inExpectedParams != null && inExpectedParams.length > 0) {
String msg = inThrowable.toString();
Serializable[] params = e.getI18NBoundMessage().getParams();
assertEquals(msg, inExpectedParams.length, params.length);
for(int i = 0; i < inExpectedParams.length; i++) {
if (inExpectedParams[i] != IGNORE) {
assertEquals(msg, inExpectedParams[i], params[i]);
}
}
}
return e;
}
/**
* Verifies if the supplied exception has the specified message.
*
* @param inThrowable the exception to be verified.
* @param inExpectedMessage the expected exception message.
* @param inExactMatch true, if the specified message should match
*
* @return the supplied inThrowable value.
*/
public static Throwable assertException(Throwable inThrowable,
String inExpectedMessage,
boolean inExactMatch) {
assertNotNull(inThrowable);
if (inExactMatch) {
assertEquals(inExpectedMessage, inThrowable.getMessage());
} else {
assertTrue(inThrowable.getMessage(),
inThrowable.getMessage().contains(inExpectedMessage));
}
return inThrowable;
}
/**
* Creates an instance that will test for failures with I18NExceptions
*
* @param inExpectedMessage the expected message in the exception, null
* if the message need not be tested.
* @param inExpectedParams the expected parameters to the message, null
* if the parameters need not be tested.
*
* @throws Exception if there are unexpected failures.
*/
protected ExpectedFailure(I18NMessage inExpectedMessage,
Object... inExpectedParams) throws Exception {
mExpectedMessage = inExpectedMessage;
mExpectedParams = inExpectedParams;
doRun();
}
/**
* Creates an instance that will test for failures with I18NExceptions
*
* @param inExpectedMessage the expected message. null if the message
* need not be tested.
*
* @throws Exception if there were unexpected failures.
*/
protected ExpectedFailure(I18NBoundMessage inExpectedMessage)
throws Exception {
this(inExpectedMessage == null
? null
: inExpectedMessage.getMessage(),
inExpectedMessage == null
? null
: (Object[])inExpectedMessage.getParams());
}
/**
* Creates an instance that will test for failures with any java
* Exception.
*
* @param inMessage the exception message.
*
* @throws Exception if there was an unexpected failure
*/
protected ExpectedFailure(String inMessage) throws Exception {
this(inMessage, true);
}
/**
* Creates an instance that will test for failures with any exception.
*
* @throws Exception if were was an unexpected failure
*/
protected ExpectedFailure() throws Exception {
doRun();
}
/**
* Creates an instance that will test for failures with any java
* Exception.
*
* @param inMessage the exception message.
* @param inExactMatch true if the message should match exactly, false
* if the generated message contains the supplied message.
*
* @throws Exception if there was an unexpected failure
*/
protected ExpectedFailure(String inMessage, boolean inExactMatch)
throws Exception {
mMessage = inMessage;
mExactMatch = inExactMatch;
doRun();
}
/**
* Subclasses should implement this method to execute
* code that is expected to fail with the exception of type
* <code>T<code>
*
* @throws Exception if there's a failure.
*/
protected abstract void run() throws Exception;
/**
* Runs the test code and verifies the exception.
*
* @throws Exception runs the test code and verifies the exception.
*/
@SuppressWarnings("unchecked")
private void doRun() throws Exception {
try {
run();
fail("Didn't fail!");
} catch(Exception t) {
Class expected = getExceptionClass();
assertTrue("Expected<" + expected + ">Actual<"+t.getClass()+">" + t,
expected.isInstance(t));
mException = (T) t;
if(t instanceof I18NException) {
assertI18NException(t, mExpectedMessage, mExpectedParams);
} else {
assertNull(t.getClass() + " is not an I18NException", mExpectedMessage);
}
if(mMessage != null) {
assertException(t, mMessage, mExactMatch);
}
}
}
/**
* Extracts the exception type specified as the value of parameter
* <code>T</code>from the class metadata.
*
* @return the expected exception type.
*/
private Class getExceptionClass() {
ParameterizedType pt;
Class cls = getClass();
//find the direct sub-class of this class
while(!ExpectedFailure.class.equals(cls.getSuperclass())) {
cls = cls.getSuperclass();
}
pt = (ParameterizedType) cls.getGenericSuperclass();
Type[] t = pt.getActualTypeArguments();
assertEquals(1, t.length);
return (Class) t[0];
}
private I18NMessage mExpectedMessage;
private Object[] mExpectedParams;
private T mException;
private String mMessage;
private boolean mExactMatch;
/**
* A parameter value that can be used to indicate that a parameter
* may be ignored when testing for failure in
* {@link #assertI18NException(Throwable, I18NMessage, Object[])} or
* {@link #ExpectedFailure(I18NMessage, Object[])}
*/
public static final Serializable IGNORE = new Serializable(){};
}