It’s often desirable to refactor common assertions into a seperate method when doing unit tests. Junit e.g. does not provide a default method to test if a Collections is empty or contains a certain single element. The straight forward idea of refactoring the assertions into a method works but the stacktrace shown if the method fails is annoying:
public class TestCaseHelper {
public static void assertContainsOnly(Object item, List result) {
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
Assert.assertEquals(item, result.get(0));
}
Failures of this method will give a stacktrace like the following:
junit.framework.AssertionFailedError: expected:<1> but was:<0> at TestCaseHelper.assertContainsOnly(TestCaseHelper.java:15) at TestCaseHelperDemo.testAssertContainsOnly(TestCaseHelperDemo.java:14) ...
I found this highly annoying because I wanted the topmost frame to be
at TestCaseHelperDemo.testAssertContainsOnly(TestCaseHelperDemo.java:14)
and I want to provide a custom message to the set of three assertions.
The solutions lies in mangling the exceptions stackstrace. java.lang.Exception provides a method setStackTrace since 1.4 which is very usefull for this:
public class TestCaseHelper {
public static void assertContainsOnly(Object item, List result) {
try {
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
Assert.assertEquals(item, result.get(0));
} catch (AssertionFailedError x) {
String message = "Expected exacly one item in List. Expected <" + item + ">“;
throwMangledException(message);
}
}
private static void throwMangledException(String message) {
AssertionFailedError e = new AssertionFailedError(message);
e.fillInStackTrace();
StackTraceElement[] stackTrace = e.getStackTrace();
final int FRAMES_TO_POP = 2;
StackTraceElement[] newStackTrace = new StackTraceElement[stackTrace.length-FRAMES_TO_POP];
System.arraycopy(stackTrace, FRAMES_TO_POP, newStackTrace, 0, newStackTrace.length);
e.setStackTrace(newStackTrace);
throw e;
}
}
The trick is to get a stacktrace by invoking fillInStackStrace, to pop the topmost frame and to throw a new Exception with the mangled stacktrace. The stacktrace reported now is reduced to what I’m interested in:
junit.framework.AssertionFailedError: Expected exacly one item in List. Expected <java.lang.Object@1f5d386> at de.projectparcel.junit.TestCaseHelperDemo.testAssertContainsOnly(TestCaseHelperDemo.java:14) ...