Tuesday, August 24, 2004

JUnit work around for lost exceptions

Here's the scenario: a unit test throws an uncaught exception, but then so does tearDown(). What happens? TestCase.runBare() says:

public void runBare() throws Throwable {
    setUp();
    try {
        runTest();
    } finally {
        tearDown();
    }
}

See the problem? If tearDown() throws, then runBare() loses any exception thrown by runTest() (which runs your unit test). [See the Java Language Specification, §11.3.] Rarely is this what you actually want since the lost exception was the original cause of test error. The solution—override runBare():

public void runBare() throws Throwable {
    Throwable thrown = null;

    setUp();

    try {
        runTest();

    } catch (final Throwable t) {
        thrown = t;

    } finally {
        try {
            tearDown();

            if (null != thrown) throw thrown;

        } catch (Throwable t) {
            if (null != thrown) throw thrown;
            else throw t;
        }
    }
}

The idea is simple. Store the exception from runTest() and rethrow it after running tearDown(). Make sure to throw any exception from tearDown() otherwise.

UPDATE: My brain finally caught up with my fingers and I realize that the sample solution is unnecessarily complex. Better is:

public void runBare()
        throws Throwable {
    Throwable thrown = null;

    setUp();

    try {
        runTest();

    } catch (final Throwable t) {
        thrown = t;

    } finally {
        try {
            tearDown();

        } finally {
            if (null != thrown) throw thrown;
        }
    }
}

2 comments:

Anonymous said...

why (null != thrown) and not (thrown != null)?

Brian Oxley said...

Why? I just like the keyword first for aesthetic reasons. I also find it slightly easier to pick out when scanning vertically down a long listing of code.

I would have been better off with a slightly different idiom:

try {
tearDown();

} finally {
if (null != thrown) throw thrown;
}

And now I am relying on the fact that the earlier exception is swallowed by the JVM when the latter throws. Making lemondade, as it were.