Loading
BLOG 開発者ブログ

2025年12月24日

Java JUnit5における初期処理の自動化とよく使うMockito

はじめに

こんにちは。murakami.kです。
この記事はアイソルートAdvent Calendar 2025 24日目の記事です。

皆さん、JavaでPT(単体テスト)を書く際に

  • テストごとに毎回同じ処理を書くのが面倒
  • mockってどう書くんだっけ?」と毎回調べ直している
  • Mockの設定や期待値の書き方に自信がない

などと感じることはないでしょうか。

この記事では、

  • JUnit5のExtensionとServiceLoaderを使った「共通初期処理の自動化方法
  • 戻り値あり/なし、例外、staticメソッドなど、よくあるMockitoのモック定義方法
  • verify や ArgumentCaptor を使った呼び出し回数や引数内容の検証方法

を、そのままテストコードに落とし込める形で説明します。

JUnit5やMockitoの機能を闇雲に使うのではなく、可読性や保守性の観点からも「何を検証したいテストなのか」を意識するきっかけとして本記事の内容を参考にしてもらえれば嬉しいです。

また、「毎回同じテスト準備コードを書いている」「モックの書き方で手が止まることがある」
そんな方が、テストコードを書く際の迷いや手戻りを減らすための整理資料として、
さらに、PTを実施する際の振り返りなどでも役立てられれば幸いです。

目次

  1. PTで毎回必要になる初期処理の共通・自動化
    1. 設定方法
    2. 実行例
  2. Mockの活用
  3. まとめ

PTで毎回必要になる初期処理の共通・自動化

PTではテストごとに同じ前提条件で処理が実行されることが非常に重要です。
前のテストの影響が残った状態で次のテストが実行されると、
「たまたま通る」「実行順によって結果が変わる」といった不安定なテストになってしまいます。

そのため、多くのPTでは次のような前処理・後処理を行う事がほぼ必須になります。

  • テスト用のシステムプロパティや設定値をセット
  • テスト実行前に初期化しておきたい共通処理の実行
  • テスト実行後のクリーンアップ

これらを行わない場合、

  • 他のテストの設定が影響して失敗する
  • 結合試験で単体レベルのバグが発生する
  • テストの実行順によって結果が変わる

といった問題が発生しやすくなります。

JUnitでは @BeforeAll や @AfterAll などを使うことでこうした前処理・後処理を書くことは可能です。
しかし、「どの機能のPTでも必ず設定したい」みたいなケースではこれを各テストに書くのは管理コストも含め少々大変です。

    そこで本記事では、JUnit5 の Extension と ServiceLoader を組み合わせてこれらの共通初期処理をテスト全体に”自動適用”する方法を紹介します。

     

    設定方法

    フォルダ構成

    以降で作成していくファイルを含め、フォルダ構成のイメージは下記の通りです。

    src/
    ├─ test/
    |└─ java/
    | └─ sample/junit5/
    |  ├─ SampleTest.java
    |  └─ extension/
    |   └─ HogeExtension.java
    └─ resources/
     ├─ junit-platform.properties
     └─ META-INF/services/
      └─ org.junit.jupiter.api.extension.Extension

    実行したい処理(Extension)を作成

    以下のように、自動実行したい処理を作成します。
    今回はテスト全体の開始時と終了時に1回ずつだけ実行する beforeAll と afterAll のみ書いています。
    なお、本記事ではテスト全体の開始時・終了時に一度だけ実行される処理を例にしていますが、各テストメソッドの実行前後で毎回処理を行いたい場合は、beforeEach や afterEach を実装することで同様に対応できます。

    例えば

    • 各テストごとにオブジェクトを初期化したい
    • テストメソッドごとに状態を必ずリセットしたい

    といったケースでは、beforeEach や afterEachを使うほうが適しています。

    package sample.junit5.extension;
    
    import org.junit.jupiter.api.extension.AfterAllCallback
    import org.junit.jupiter.api.extension.BeforeAllCallback
    import org.junit.jupiter.api.extension.ExtensionContext
    
    // BeforeAllCallBack, AfterAllCallBack をimplementsしてそれぞれ オーバーライドする
    public class HogeExtension implements BeforeAllCallback, AfterAllCallback {
        @Override
        // テスト全体の開始前に1度だけ実行
        public void beforeAll(ExtensionContext context) throws Exception {
            setupTest();
        }
    
        @Override
        // テスト全体の終了後に1度だけ実行
        public void afterAll(ExtensionContext context) throws Exception {
            cleanupTest();
        }
    
        // テスト開始前に実行するメソッド例
        private void setupTest() {
            System.setProperty("aaa", "bbb");
            System.out.println("セットアップ完了:PT開始");
        }
    
        // テスト終了後に実行するメソッド例
        private void cleanupTest() {
            System.clearProperty("aaa");
            System.out.println("クリーンアップ完了:PT終了");
        }
    }

    「org.junit.jupiter.api.extension.Extension」ファイルを作成

    以下を記載し、ServiceLoaderが読み込むExtension一覧を定義します。
    ちなみに ServiceLoader とは、クラスパス上に定義された実装クラスを実行時に自動で読み込む仕組みです。
    JUnit5 では、この仕組みを使って Extension が検出・適用されています。

    sample.junit5.extension.ServiceLoaderExtensionClass
    

    「junit-platform.properties」ファイルを作成

    以下を記載し、ServiceLoaderを使った自動登録を有効にします。
    この設定を行わない場合、ServiceLoader によって Extension が検出されず、@ExtendWith などで 明示的に Extension を指定しない限り処理は実行されません。

    junit.jupiter.extensions.autodetection.enabled=true
    

    以上で設定は完了です。
    あとはTestを実行するだけですべてのTestでHogeExtensionで書いた内容が反映されるようになります。

     

    実行例

    サンプルコード

    import org.junit.jupiter.api.Test;
    
    class SampleTest {
        @Test
        void testCase1() {
            System.out.println("テスト1");
        }
    
        @Test
        void testCase2() {
            System.out.println("テスト2");
        }
    }
    

    実行結果

    セットアップ完了:PT開始
    テスト1
    テスト2
    クリーンアップ完了:PT終了

    Mockの活用

    次はMockの活用方法について紹介します。

    PTではテスト対象を明確にし、その機能だけをテストするために、
    それ以外のロジックや機能の呼び出し、DB接続などは 期待通り動くダミー(モックなど) を活用することが一般的です。
    また、まだ完成していない機能や外部リソースなどがある場合にも有用とされています。

    一方で、よほど書き慣れている方以外は、いざモックを書こうとすると
    「どうやるんだっけ」「こういう時はどうするんだっけ」などと悩み、
    調べて試して失敗して…と、結果的にモックを作ることに時間をかけてしまうことも少なからずあると思います。
    (私はまさにそうでした。)

    そんな人のためにもモックを要領よく扱うために簡潔にまとめてみます。

     

    Mockito

    この記事では、Javaのモックフレームワークである Mockito について記載します。
    Mockitoのモックは、大きく分けて以下の2種類に分けられます。

    • インスタンスモック(通常のmock)
    • staticメソッドのモック(MockedStatic)

    モックの定義

    インスタンスモック

    // 対象のモック定義
    HogeClass mockedHoge = mock(HogeClass.class);
    
    /** 戻り値あり */
    // 正常処理を想定
    when(mockedHoge.hogeMethod(eq("test1"), any())).thenReturn("returnValue");
    // エラー処理を想定
    when(mockedHoge.hogeMethod(eq("test1"), any())).thenThrow(new RuntimeException("error"));
    
    /** 戻り値なし(void) */
    // 正常処理を想定
    doNothing().when(mockedHoge).hogeVoidMethod(eq("test1"), any());
    // エラー処理を想定
    doThrow(new RuntimeException("error")).when(mockedHoge).hogeVoidMethod(eq("test1"), any()));
    

    staticメソッドのモック

    // 対象のモック定義
    MockedStatic mockedHoge = mockStatic(HogeClass.class);
    
    /** 戻り値あり */
    // 正常処理を想定
    mockedHoge.when(() -> HogeClass.hogeMethod(eq("test1"), any())).thenReturn("returnValue");
    // エラー処理を想定
    mockedHoge.when(() -> HogeClass.hogeMethod(eq("test1"), any())).thenThrow(new RuntimeException("error"));
    
    /** 戻り値なし(void) */
    // 正常処理を想定
    mockedHoge.when(() -> HogeClass.hogeVoidMethod(eq("test1"), any())).thenAnswer(invocation -> null);
    // エラー処理を想定
    mockedHoge.when(() -> HogeClass.hogeVoidMethod(eq("test1"), any())).thenThrow(new RuntimeException("error"));
    

    インスタンスモック、staticモックそれぞれについて、
    戻り値の有無やエラーを返したい場合の実装例をまとめました。

    メソッドの引数に指定している eq() や any() は、Mockitoが提供する引数マッチャーです。

    • eq:指定した値と一致する場合のみマッチ
    • any():どんな値が渡されてもマッチ

    このほかにも anyInt() や contains() など、さまざまなマッチャーが用意されています。
    これらを使うことで、どの引数をどこまで厳密にチェックするかを引数レベルで制御できます。

    複数回呼ばれ、戻り値が変わる場合

    同じメソッドが複数回呼ばれ、呼び出し回数ごとに戻り値を変えたい場合は、
     thenReturn を連結することで簡単に実現できます。
    ( mockStatic や thenThrow についても同様です)

    when(mockedHoge.hogeMethod(eq("test1"), any()))
        .thenReturn("returnValue1")
        .thenReturn("returnValue2")
        .thenReturn("returnValue3");
    

    一部だけモック化する(それ以外は実装をそのまま使う)

    ケースとしては少ないですが、
    下記のように CALLS_REAL_METHODS を使うことで一部のメソッドのみをモック化し、それ以外は実装をそのまま使うことも可能です。

    // インスタンスモック
    HogeClass mockedHoge = mock(HogeClass.class, Mockito.CALLS_REAL_METHODS);
    
    // staticメソッドのモック
    MockedStatic mockedHoge = mockStatic(HogeClass.class, Mockito.CALLS_REAL_METHODS);
    

    モック化したメソッドの呼び出し回数を確認

    モックを定義しテストを実行した後、
    期待通りにモック化したメソッドが呼ばれたどうかは verify を使うことで呼び出し回数まで含めて簡単に確認できます。

    また、先ほどの引数マッチャーを組み合わせることで呼び出し回数だけでなく、引数が期待通りかどうかも同時に検証可能です。

    /** インスタンスモック */
    // 第一引数が"test1"でhogeMethodが1回だけ呼ばれた
    verify(mockedHoge, times(1)).hogeMethod(eq("test1"), any());
    
    /** staticメソッドのモック */
    // hogeMethodが3回呼ばれた
    mockedCalculator.verify(() -> HogeClass.hogeMethod(any(), any()),times(3));
    

    ※ 注意事項

    MockedStaticは必ず close() をすること

    staticメソッドをモック化した場合、その影響はグローバルに影響するため後続のテストにもモック状態が引き継がれてしまい、意図しないテスト失敗の原因になります。

    以下のように明示的に close() を呼び出すことで、以降の処理への影響を防ぐことは可能です。

    // モックのクリーンアップ
    mockedHoge.close();
    

    しかしこの書き方には、次のような問題があります。

    • テスト途中で例外が発生した場合、 close() が呼ばれない可能性がある
    • 単純に close() の書き忘れが発生しやすい

    try-with-resources を使う方法(推奨)

    これらの問題を未然に防ぐ方法として、
    try with resource文を使った書き方があります。

    try (MockedStatic<HogeClass> mockedHoge = mockStatic(HogeClass.class)) {
    // static モックが有効な範囲
    }
    // ← ここで必ず close() される
    

    この方法であれば、

    • close() を明示的に書く必要がない
    • テスト途中で例外が発生しても、確実に close() される

    といったメリットがあり、
    staticモックによるテスト間の副作用を安全に防ぐことができます。

    補足

    なお、通常のインスタンスモック(mock())については、
    テストメソッド単位で参照が破棄されるため、明示的な close() は不要です。

     

    引数の内容を検証したい場合(ArgumentCaptor)

    ここまでで、

    • モックの定義方法
    • 呼び出し回数や引数の検証(verify)
    • staticモックを安全に扱う方法

    について説明してきました。

    verify(mockedHoge).hogeMethod(eq("test1"), any());
    

    このように eq() や any() 等を使えば、
    「どんな値で呼ばれたか」を条件としてチェックすることは可能です。

    一方で、実際のテストでは次のようなケースも少なくありません。

    • 渡されたオブジェクトの中身まで詳しく検証したい
    • どんな値が渡ってきたのかを確認したい
    • equals() だけでは検証しきれない複雑なオブジェクトがある

    そのような場合に利用できるのが ArgumentCaptor です。

    ArgumentCaptorの基本的な使い方

    ArgumentCaptor は、実際にメソッドに渡された引数を捕捉(キャプチャ)するための仕組みです。

    // 検証したい引数の型を指定してArgumentCaptorを定義
    ArgumentCaptor userArgCap = ArgumentCaptor.forClass(User.class);
    
    // 第一引数の値をキャプチャする
    verify(mockedHoge, times(1)).hogeMethod(userArgCap.capture(), any());
    
    // キャプチャした値を取り出す
    User userArg = userArgCap.getValue();
    
    // 値を検証する
    assertEquals("TestId", userArg.getId());
    assertEquals("TestName", userArg.getName());
    

    ポイント

    • ArgumentCaptor の定義では キャプチャしたい引数の型(クラス)を指定する
    • capture() は verify の引数として使用する
    • getValue() で、実際に渡された値を取得できる

    eq() との使い分け

    eq()

    • 「この値で呼ばれたか」を条件としてチェック
    • 単純な値比較に向いている
    • テストが簡潔になりやすい

    ArgumentCaptor

    • 「実際に渡された値は何か」を取り出してチェック
    • オブジェクトの中身を細かく確認したい場合に向いている
    • 検証の自由度が高い

    どちらも「引数を検証する」という目的は同じです。
    そのため、テストの可読性や書きやすさを基準に選ぶ のがポイントだと考えています。

    あくまで一例ですが、私の場合は次のように使い分けています。

    • eq()
      String や Integer、単純なオブジェクトなど、
      期待する値をテスト内にそのまま書きやすい場合
    • ArgumentCaptor
      複雑なオブジェクトなど、
      期待する値を書くのが大変な場合や、詳細な検証が必要な場合

    まとめ

    JUnit5やMockitoは、ただ使うだけでなく、初期処理の共通化やモックの設計を意識して活用することが重要です。

    「毎回同じ処理を書いていないか」「モックが複雑になりすぎていないか」といった点を意識しながら、PTの運用を考えることが、結果的にテストコードの書きやすさや保守性の向上につながると考えています。

    JUnitやMockitoの機能に振り回されるのではなく、テストの目的に合った形で使いこなすことを意識し、本記事の内容が少しでも参考になれば幸いです。

    murakamikのブログ