JUnit: 例外クラスのtoStringが状態依存していてはまった

Teeda のプロジェクトでlogicクラスのテストを書いて動かしたら context(facesContext)が null だと言われる。

しかも元々のエラー発生箇所が表示されないため、エラーになるたびにげんなりする。
これはよろしくない、テストを書くのが億劫になりすぎる!!!!
という経緯で調べてみた。


結局のところプロジェクト側で用意されていた例外クラス MyException の toString 内で
FacesContext.getCurrentInstance()
していたのがよろしくなかった。
テスト対象のメソッドを実行しているときは普通に toString(の中で FacesContext のインスタンスを取得)できるが、例外オブジェクトは S2コンテナが破棄された後もJUnitによって受け渡され、そこで toString を呼ぶと当然 FacesContext なにそれ、となる。

S2コンテナ初期化
テスト実行・例外オブジェクト生成 ← ここのエラーについて知りたいのに
S2コンテナ破棄
テスト結果表示 ← ここでぬるぽ

S2FrameworkTestCase を見てみるとこうなっている。
テスト結果表示は、この runBare の後で行われているっぽい。

    public void runBare() throws Throwable {
        setUpContainer();
        try {
            setUp();
            try {
                setUpForEachTestMethod();
                try {
                    container.init(); // S2コンテナ初期化
                    try {
                        setUpAfterContainerInit();
                        try {
                            bindFields();
                            try {
                                setUpAfterBindFields();
                                try {
                                    doRunTest(); // テスト実行
                                } finally {
                                    tearDownBeforeUnbindFields();
                                }
                            } finally {
                                unbindFields();
                            }
                        } finally {
                            tearDownBeforeContainerDestroy();
                        }
                    } finally {
                        container.destroy(); // S2コンテナ破棄
                    }
                } finally {
                    tearDownForEachTestMethod();
                }
            } finally {
                tearDown();
            }
        } finally {
            tearDownContainer();
        }
    }

S2フレームワークを使っている場合はS2コンテナのライフサイクルが絡むので NPE が起こるけど、もうちょっと抽象的な話としてはエラー発生のときと toString を呼んだときとで状態が変わっているのが問題。


S2コンテナ使わずに素朴に試してみた。

package exception;

public class MyException extends RuntimeException {

    private int value;
    
    public MyException(){
        value = 0;
    }
    
    @Override
    public String toString() {
        value++; // toString が呼ばれるたびに状態が変わる
        return "value=" + value;
    }
}
import org.junit.Test;
import exception.MyException;

public class FooTest {

    @Test
    public void test() throws Exception{
        try {
            throw new MyException();
        } catch (Exception e) {
            System.err.println( e.toString() ); //=> value=1
            throw e;
        }
    }
    
}

JUnitの結果表示(value=2):
2012-11-17 11:03:50

そうなりますよねー。