概要

JUnit 5 の assertEquals や assertTrue でテストを書いていると、「期待値と実際値のどちらが先だったか」「コレクションの中身をどう検証すればいいか」で手が止まることがあります。AssertJ は assertThat(actual).isEqualTo(expected) の形で主語を先に書く流暢な API を提供し、IDE の補完と組み合わせることで「何を検証しているか」がテストコードを読むだけで分かるようになります。この記事では、注文処理の結果検証を題材に、リストの要素検証(extracting + containsExactly)、例外メッセージの検証(assertThatThrownBy)、文字列の部分一致検証を一通り扱います。JUnit 5 標準との書き方の違いも示すので、チームへの導入判断にも使えるはずです。特にコレクションの中身を細かく検証する場面では、AssertJ で劇的に読みやすくなります。

使いどころ

注文一覧 API のレスポンスに含まれる商品名と数量を extracting + containsExactly でまとめて検証する

バリデーションエラー時の例外メッセージに必要な情報が含まれていることを assertThatThrownBy で検証する

帳票出力の結果文字列がヘッダー・明細・フッターの順に構成されていることを contains / startsWith / endsWith で検証する

ドメインオブジェクトの複数フィールドを satisfies や extracting で読みやすく確認する

リストの順序が仕様に含まれる検索結果で、containsExactly と hasSize を組み合わせて回帰を防ぐ

コード例

OrderValidationTest.java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import java.util.List;
import static org.assertj.core.api.Assertions.*;

class OrderValidationTest {

    record OrderItem(String productName, int quantity, int unitPrice) {
        int subtotal() { return quantity * unitPrice; }
    }

    record Order(String orderId, List<OrderItem> items) {
        int totalAmount() {
            return items.stream().mapToInt(OrderItem::subtotal).sum();
        }
        String summary() {
            return "注文番号: %s / 合計: %,d円 / 明細数: %d"
                .formatted(orderId, totalAmount(), items.size());
        }
    }

    static Order createSampleOrder() {
        return new Order("ORD-2024-001", List.of(
            new OrderItem("ボールペン(黒)", 10, 150),
            new OrderItem("コピー用紙 A4", 5, 480),
            new OrderItem("付箋(大)", 3, 320)));
    }

    @Nested @DisplayName("コレクション検証")
    class CollectionTests {
        @Test @DisplayName("商品名と数量を一括検証する")
        void extractFields() {
            assertThat(createSampleOrder().items())
                .hasSize(3)
                .extracting(OrderItem::productName, OrderItem::quantity)
                .containsExactly(
                    tuple("ボールペン(黒)", 10),
                    tuple("コピー用紙 A4", 5),
                    tuple("付箋(大)", 3));
        }
    }

    @Nested @DisplayName("文字列検証")
    class StringTests {
        @Test @DisplayName("サマリーに必要な情報が含まれている")
        void summaryContainsInfo() {
            assertThat(createSampleOrder().summary())
                .startsWith("注文番号: ORD-2024-001")
                .contains("4,860円")
                .contains("明細数: 3");
        }
    }

    @Nested @DisplayName("例外検証")
    class ExceptionTests {
        static Order createOrder(String orderId, List<OrderItem> items) {
            if (orderId == null || orderId.isBlank()) throw new IllegalArgumentException("注文番号は必須です");
            if (items == null || items.isEmpty()) throw new IllegalArgumentException("明細が空の注文は作成できません");
            return new Order(orderId, items);
        }

        @Test @DisplayName("注文番号が空だと例外が発生する")
        void throwsWhenBlank() {
            assertThatThrownBy(() -> createOrder("", List.of()))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("注文番号は必須です");
        }
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record と組み合わせると extracting(Order::productName) のようにメソッド参照で型安全にフィールドを取り出せる。

Java 17
// Java 17: record でテストデータ準備が簡潔
record Order(String productName, int quantity) {}
assertThat(orders)
    .extracting(Order::productName, Order::quantity)
    .containsExactly(tuple("ボールペン", 10), tuple("ノート", 5));

Library Comparison

AssertJ流暢な API でテストの可読性を上げたいとき。コレクション検証が強力で、業務テストで最も効果を発揮する。依存追加が必要。assertEquals で事足りるシンプルなテストには過剰な場合がある。
JUnit 5 Assertions(標準)追加依存なしで使える。assertEquals / assertThrows で基本的な検証はカバーできる。コレクションの要素検証が冗長になる。引数の順序を間違えやすい。
HamcrestassertThat + Matcher の組み合わせで柔軟な検証を行いたいとき。JUnit 4 時代からの資産がある場合。IDE 補完が AssertJ ほど効きにくい。新規プロジェクトでは AssertJ の方が推奨される傾向。

注意点

assertThat は org.assertj.core.api.Assertions.assertThat をインポートすること。Hamcrest の assertThat と混同しやすい

isEqualTo は equals() で比較する。record なら自動生成される equals で比較されるが、通常クラスでは equals を実装していないと参照比較になる

extracting でフィールドを取り出す際、メソッド参照(Order::productName)を使うと型安全になる。文字列指定はリファクタリングに弱い

assertThatThrownBy に渡すラムダの中で例外が発生しなかった場合、AssertJ が AssertionError を投げる

AssertJ は JUnit 5 と併用するライブラリであり、テストランナーの代替ではない。@Test は JUnit 5 のものを引き続き使う

containsExactlyInAnyOrder は順序を無視するため、ソート仕様を確認したいテストでは不適切になる

便利なアサーションを使いすぎると、かえってチーム内で読みにくい場合がある。頻出パターンから段階的に導入する

FAQ

AssertJ と JUnit 5 のアサーションを混在させてもよいですか。

技術的には問題ありませんが、チーム内でスタイルが混在すると可読性が下がります。導入するなら全テストで統一する方針が望ましいです。

containsExactly と containsExactlyInAnyOrder の違いは何ですか。

containsExactly は順序も含めて一致を検証します。containsExactlyInAnyOrder は順序を問わず要素の一致だけを検証します。

カスタムアサーションを作る場面はどんなときですか。

同じ検証パターンが複数テストに重複するときに AbstractAssert を継承して作ります。3回以上同じ検証を書いたら抽出を検討する目安です。

AssertJ はどんな場面で特に効果がありますか。

コレクション、Optional、例外、文字列のように複数条件を読みやすく並べたい場面で効果が大きいです。単純な数値比較だけなら JUnit 標準でも十分です。

soft assertions は使うべきですか。

DTO や画面レスポンスの複数フィールドをまとめて確認したい場面では有効です。ただし unrelated な検証まで1テストに詰め込むと意図がぼやけるため、対象を絞って使います。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます