notice

Cut the Stacktrace for custom Junit Assertions

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)
...
store

{ Comments are closed! }

conditions
Bear
report