よしたろうブログ

設計・人文知・歴史・哲学・漫画とかの話が好きです。

単体テストにおいて、実行時例外が発生しうるメソッドは一つだけにしないといけません『Only one method invocation is expected when testing runtime exceptions java:S5778』

Sonar に怒られました

Only one method invocation is expected when testing runtime exceptions java:S5778

どういう意味?

JUnitのテストメソッド内で例外が発生することを検証する場合の非推奨な実装だと怒られています。実装はこんな感じ

    @Test
    @DisplayName("API実行時、期待する Exception がスローされること")
    void getException() {
        // テスト実行・結果確認
        var exception =
                assertThrows(WalletException.class,
                        () -> testTarget.getException(
                                new HttpClientErrorException(
                                        HttpStatus.NOT_FOUND
                                        , "Not Found"
                                        , "{\"message\":\"Error message\"}".getBytes()
                                        , CHARSET)
                                ));
    }
}

非推奨のコード例では、getException() や new Exception() のように複数のメソッド呼び出しがテストコード内に含まれています。この場合、例外が発生するのはgetException()なのか、new Exception()なのか明示的にはわかりません。複数のメソッド呼び出しが含まれると、テストの明確性が低下し、意図しないメソッドが例外をスローする場合に誤ったテスト結果になる可能性があります。

解決策

コードが実行時例外を発生させることを検証する場合、テストされるコード内部で複数のメソッドコールを行わないようにし、どのメソッドコールが例外を発生させるのかを明示するのが良い方法です。

    @Test
    @DisplayName("API実行時、期待する Exception がスローされること")
    void getException() {
        // テスト準備
        var httpException = new HttpClientErrorException(
                HttpStatus.NOT_FOUND, "Not Found", "{\"message\":\"Error message\"}".getBytes(), CHARSET);
        // テスト実行/結果確認
        assertThrows(WalletException.class, () -> testTarget(httpException));
    }
}

そうすることで、テストの明瞭度が増し、意図した例外の発生を正確に検証できるようになります。また、別のメソッドが実際に例外を発生させている場合に間違ったテストが行われるのを防ぐことができます。

推奨される解決策では、HttpClientErrorException()メソッドの結果を変数に代入してから単一のメソッド呼び出しを行っています。これにより、どのメソッド呼び出しで例外が発生するかが明確になります。