2016年1月25日月曜日

[JUnit]ExpectedExceptionを使って例外のアサート

JUnitで提供されるExpectedExceptionを使って例外のアサートをしてみる。

ExpectedExceptionがなかった時代は、try-catchで例外補足してアサートしていたのでかなりの便利機能だと思う。
※@Testアノテーションのexpected属性だと、発生する例外のクラスしかアサートできなかったので、これは使い物にならなかった。

使い方


テストクラスのインスタンス変数で、ExpectedExceptionを宣言する。
ルールであることを示す、@Ruleアノテーションを設定する。
宣言時には、ExpectedException.noneを代入することで、例外が発生しないことを期待するようにしておく。
    @Rule
    public ExpectedException expectedException = ExpectedException.none();

例外をアサートする場合には、例外が発生することを期待するテストケースのメソッドで、アサートする内容をExpectedExceptionに設定する。
このケースだと、NullPointerExceptionが発生してそのメッセージには「error」が保持されていることを期待している。
    @Test
    public void nullPointerException() throws Exception {
        expectedException.expect(NullPointerException.class);
        expectedException.expectMessage("error");
        throw new NullPointerException("error");
    }

元例外(cause)をアサーとしたい場合は、expectCauseにMatcherを登録する。
この例では、causeの例外クラスがNullPointerExceptionであることをアサーとしている。
        expectedException.expect(CustomException.class);
        expectedException.expectCause(instanceOf(NullPointerException.class));
        throw new CustomException(new NullPointerException());

メッセージやcause以外の属性をアサートする場合には、カスタムのMatcherを作ると良い。
例えば、SQLExceptionのベンダーコード(getErrorCode)をアサートする場合には以下のようなMatcherを作る。
    public static class IsSqlErrorCode extends TypeSafeMatcher {

        private final int expectedSqlErrorCode;

        public IsSqlErrorCode(int expectedSqlErrorCode) {
            this.expectedSqlErrorCode = expectedSqlErrorCode;
        }

        public static IsSqlErrorCode isSqlErrorCode(int expectedSqlErrorCode) {
            return new IsSqlErrorCode(expectedSqlErrorCode);
        }

        @Override
        protected boolean matchesSafely(SQLException e) {
            return e.getErrorCode() == expectedSqlErrorCode;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("exception with the error code ")
                       .appendValue(expectedSqlErrorCode);
        }

        @Override
        protected void describeMismatchSafely(SQLException item, Description mismatchDescription) {
            mismatchDescription.appendText("error code was ")
                               .appendValue(item.getErrorCode());
        }
    }

Matcherの使い方は・・・
        expectedException.expect(SQLException.class);
        expectedException.expect(IsSqlErrorCode.isSqlErrorCode(1));
        throw new SQLException("sql error", null, 1);