23 人が閲覧(直近 30 日) テスト

PowerMockitoとは?使い方と@PrepareForTestの指定ルール、Mockito 5.24へ移行する判断

PowerMockitoとは?使い方と@PrepareForTestの指定ルール、Mockito 5.24へ移行する判断

PowerMockitoは、PowerMockがMockito向けに用意したAPIです。素のMockitoでは差し替えられなかった静的メソッド・コンストラクタ・privateメソッドをMock化するために広く使われてきました。ただし本体のPowerMockは2020年11月の2.0.9を最後にリリースが止まり、Mockito 4以降とは組み合わせられません。この記事では、PowerMockitoの書き方と@PrepareForTestの指定ルールを、openjdk 25.0.4.1で実際に動かした結果とともに示し、どこまでをMockito 5.24の標準機能へ移せるかまで整理します。

まとめ

  • PowerMockitoはPowerMockのMockito用API。読みは「パワーモッキート」。最新公開版は2.0.9(2020年11月2日)で、JUnit 4とTestNG向けのモジュールはありますが、JUnit 5のJupiterには対応していません。
  • 依存はpowermock-module-junit4とpowermock-api-mockito2の2.0.9に、Mockito 3.x系とJUnit 4を組み合わせます。Mockito 4.11.0や5.24.0を入れるとテストが起動しません。
  • @PrepareForTestには「書き換えたいメソッドを定義しているクラス」を書きます。例外はjava.*のシステムクラスとwhenNewで、この2つはnewや呼び出しを行う側のクラスを指定します。
  • JDK 17以降では --add-opens が無いとInaccessibleObjectExceptionで落ちます。JDK 25では、テストが触れるjava.lang・java.io・java.utilをそれぞれ開ける必要がありました。
  • 新規のテストはMockito 5.24の標準機能で書けます。静的メソッドはmockStatic、コンストラクタはmockConstruction、enumはmockSingleton、静的初期化子の抑止も5.24で入りました。privateメソッドとnativeメソッドの差し替えは対象外です。

PowerMockitoとPowerMock・Mockitoの関係

Mockitoはインターフェースや通常のクラスをMock化するJavaの標準的なライブラリです。PowerMockは独自のクラスローダでテスト対象のクラスを読み直し、バイトコードを書き換えることで、Mockitoが設計上触れなかった領域を差し替えます。そのPowerMockをMockitoと同じ when(...).thenReturn(...) の書き方で呼ぶための入口がPowerMockitoです。テストの基本構造はJUnitの基本と書き方、モックとスタブの役割の違いはモックとスタブの違いとテストダブル5分類で扱っています。

PowerMockが担ってきたのは、静的メソッド・コンストラクタ(newで生成されるインスタンス)・privateメソッド・finalクラスとfinalメソッドの4領域です。このうちfinalはMockito 2系のinline mock maker、静的メソッドはMockito 3.4.0(2020年7月10日)、コンストラクタは3.5.0(2020年8月15日)の mockConstruction() で本体が扱えるようになりました。privateメソッドとnativeメソッドの差し替えは、2026年10月時点でもMockitoの対象外です。

PowerMock側は、GitHubのリリースもMaven Centralの最新も2.0.9のままです。リポジトリはアーカイブされていませんが、JUnit 5対応を求めるissue #830は2017年8月7日の作成から今もopenです。

PowerMockitoの依存設定と実行環境

Maven・Gradleの依存とMockitoの上限

PowerMockitoは単体では動かず、JUnit 4と結合するモジュール、Mockitoへのブリッジ、Mockito本体、JUnit 4の4つを揃えます。PowerMock 2.0.9のタグにあるbuild.gradleは mockitoVersion = "3.3.3"、junitVersion = "4.12" を宣言しています。以下は今回JDK 25で動作を確かめた組み合わせです。

groupId artifactId version
junit junit 4.13.2
org.mockito mockito-core 3.12.4
org.powermock powermock-module-junit4 2.0.9
org.powermock powermock-api-mockito2 2.0.9

scopeはすべてtestです。Gradleでは次の4行になります。

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'

mockito-coreだけを4.11.0や5.24.0へ上げると、PowerMockを使うテストはすべて NoClassDefFoundError: Could not initialize class org.mockito.Mockito で止まりました。リリースブランチには2022年2月にMockito 4.3.1との互換を取る変更がマージされていますが、その版は公開されていません。Mockitoを上げたいなら、PowerMockを外す作業が先になります。

JDK 17以降で出るInaccessibleObjectExceptionの回避

Java 17以降はjava.baseの内部へのリフレクションが既定で禁止されています。PowerMock 2.0.9のテストを何も設定せずopenjdk 25.0.4.1で実行すると、テストに入る前に次の例外で落ちました。

java.lang.reflect.InaccessibleObjectException: Unable to make private final native void java.lang.Object.wait0(long) throws java.lang.InterruptedException accessible: module java.base does not "opens java.lang" to unnamed module

回避するには、テスト実行時のJVM引数に --add-opens を足します。厄介なのは、開けるパッケージがテストの中身で増えることです。java.langだけを開けた状態では静的メソッドとprivateのテストは通り、File を差し替えるwhenNewのテストがjava.ioで落ちました。Parameterizedと併用したテストはjava.utilで落ちています。3つを開けて、今回のテストはすべて通りました。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.5.4</version>
  <configuration>
    <argLine>--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED</argLine>
  </configuration>
</plugin>

エラーメッセージの does not "opens java.xxx" に出たパッケージを1つずつ足していく運用になります。JDKを上げた直後にPowerMockのテストだけが落ちた場合は、まずこのメッセージを確認してください。JDK 21と25の差分はJava 21と25の違いと移行で止まる箇所にまとめています。

PowerMockRunnerと他のRunnerの併用設定

テストクラスには @RunWith(PowerMockRunner.class) を付けます。JUnit 4のRunnerはクラスに1つしか付けられないため、ParameterizedやSpringJUnit4ClassRunnerを使っているテストでは衝突します。この場合はPowerMockRunnerを外側に置き、本来のRunnerを @PowerMockRunnerDelegate に渡します。

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Parameterized.class)
@PrepareForTest(TaxTable.class)
public class TaxParamTest {
    @Parameters public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] { {8, 1080}, {10, 1100} });
    }
    private final int rate;
    private final int expected;
    public TaxParamTest(int rate, int expected) { this.rate = rate; this.expected = expected; }

    @Test public void price() {
        PowerMockito.mockStatic(TaxTable.class);
        when(TaxTable.rateOf("food")).thenReturn(rate);
        assertEquals(expected, new PriceService().priceWithTax("food", 1000));
    }
}

2つのパラメータとも成功しました。

@PrepareForTestに指定するクラスの決め方

PowerMockitoでモックが効かないときは、@PrepareForTestに書くクラスを確認します。静的メソッドとwhenNewでは、指定する側が異なります。原則は「書き換えたいメソッドを定義しているクラス」です。複数ある場合は @PrepareForTest({TaxTable.class, ReportWriter.class}) のように配列で渡します。

Mock化したいもの @PrepareForTestに書くクラス
自作クラスの静的メソッド そのメソッドを定義しているクラス
finalクラス・finalメソッド そのメソッドを定義しているクラス
privateメソッド そのメソッドを持つテスト対象クラス
システムクラス(java.*)の静的メソッド それを呼び出している側のクラス
コンストラクタ(whenNew) newを実行している側のクラス

反転する理由は2行で別です。公式wikiのMock-Systemは、java.*のクラスはPowerMockがテスト用に準備できないため、静的メソッドを呼び出しているクラスを準備するよう説明しています。MockConstructorのページも、PowerMockはnewを実行するクラスからの呼び出しを横取りするだけなので、生成される側のクラスは準備しないと書いています。生成先が自作クラスでも同じです。

実際に、new File(...) を呼ぶReportWriterのテストで @PrepareForTest(File.class) と書くと、whenNewは効かずに本物の createNewFile() が実行されてIOExceptionになり、最後のverifyが次のメッセージで失敗しました。

Wanted but not invoked:
-> at demo.WrongPrepareTest.constructorWrongTarget(WrongPrepareTest.java:19)
Actually, there were zero interactions with this mock.

「Wanted but not invoked」と「zero interactions」が並んで出たら、モックしたインスタンスが一度も使われていない状態です。PowerMockitoのテストでは、@PrepareForTestが定義側と呼び出し側のどちらであるべきかをまず見直してください。

PowerMockitoによるstatic・new・privateのモック化

静的メソッドの差し替えと検証:mockStatic・verifyStatic

以下のテストは、次の2クラスを対象にしています。TaxTable.rateOf() は本来DBから税率を引く想定で、テスト環境では例外を投げます。

public class TaxTable {
    public static int rateOf(String category) { throw new IllegalStateException("DB not available"); }
}

public class PriceService {
    public int priceWithTax(String category, int base) {
        return base * (100 + TaxTable.rateOf(category)) / 100;
    }
}

この静的メソッドを差し替える例です。PowerMockito.mockStatic() を呼んだ時点で、そのクラスの未スタブの静的メソッドは、戻り値の型に応じた既定値を返します。intは0ですが、参照型は一律にnullではなく、Listなどは空のコレクションになります。戻り値の指定には通常の Mockito.when を使います。

@RunWith(PowerMockRunner.class)
@PrepareForTest(TaxTable.class)
public class PowerMockTest {
    @Test public void staticMethod() {
        PowerMockito.mockStatic(TaxTable.class);
        when(TaxTable.rateOf("food")).thenReturn(8);
        assertEquals(1080, new PriceService().priceWithTax("food", 1000));
        assertEquals(0, TaxTable.rateOf("other"));

        PowerMockito.verifyStatic(TaxTable.class);
        TaxTable.rateOf("food");
    }
}

verifyStaticは2行で1組です。1行目でクラスと回数を指定し、2行目に検証したい呼び出しを書きます。実行した結果は次のとおりでした。

  • 2行目を呼ばれていない引数("water")に変えると Argument(s) are different! で失敗する
  • verifyStatic(TaxTable.class, times(2)) にすると Wanted 2 times but was 1 time. で失敗する
  • 2行目を書き忘れると、そのテスト自体は成功扱いになり、別枠の「Test mechanism」エラーとして UnfinishedVerificationException が報告される

なお引数なしの verifyStatic() は2.0で削除されており、PowerMock 1.x時代のコードは2.0.9ではコンパイルできません。

whenNewによるコンストラクタ呼び出しの差し替え

テスト対象の中で new File("/tmp/reports", name) のように生成されるインスタンスを、モックに置き換えます。@PrepareForTestにはnewを書いたReportWriterを指定します。

@Test public void constructor() throws Exception {
    File fake = mock(File.class);
    when(fake.createNewFile()).thenReturn(true);
    PowerMockito.whenNew(File.class).withArguments("/tmp/reports", "a.csv").thenReturn(fake);
    assertTrue(new ReportWriter().write("a.csv"));
    verify(fake).createNewFile();
}

withArguments には、テスト対象が実際にコンストラクタへ渡す引数をそのまま書きます。引数が食い違うとwhenNewはモックではなくnullを返し、b.csvで呼んだ検証では createNewFile() の呼び出しでNullPointerExceptionになりました。本物のインスタンスが作られる場合は、前章のように@PrepareForTestの指定を誤り、newの横取り自体が効いていません。

privateメソッドのスタブと直接呼び出し

privateメソッドの差し替えは、Mockito 5.24でも対応していない機能の一つです。PowerMockitoでは spy() したインスタンスに対して、メソッド名を文字列で渡してスタブします。カード番号をマスクするprivateメソッド mask の例です。privateStubを含むテストクラスには、@RunWith(PowerMockRunner.class) と @PrepareForTest(CardFormatter.class) を指定します。

@Test public void privateStub() throws Exception {
    CardFormatter spy = PowerMockito.spy(new CardFormatter());
    PowerMockito.doReturn("MASKED").when(spy, "mask", "1234567812345678");
    assertEquals("CARD MASKED", spy.display("1234567812345678"));
    PowerMockito.verifyPrivate(spy).invoke("mask", "1234567812345678");
}

@Test public void privateInvoke() throws Exception {
    String r = Whitebox.invokeMethod(new CardFormatter(), "mask", "1234567812345678");
    assertEquals("****-5678", r);
}

2つ目の Whitebox.invokeMethod() は、privateメソッドをリフレクションで直接呼んで戻り値を確かめる書き方です。どちらもメソッド名が文字列なので、リネームしてもコンパイルエラーにならず、実行時に初めて落ちます。privateを直接テストしたくなるのは、そのクラスが責務を抱えすぎているサインです。publicな入口から検証するか、処理を別クラスへ切り出せないかを先に検討してください。

Mockito 5.24への移行方法と対応範囲

PowerMockitoのAPIとMockitoの対応表

2026年9月23日公開のMockito 5.24.0までで、PowerMockitoの主な用途は次のように置き換えられます。

PowerMockito Mockito 5.24 追加された版
mockStatic + verifyStatic mockStatic(MockedStatic) 3.4.0
whenNew mockConstruction 3.5.0
spy + 静的メソッドの部分モック spyStatic 5.24.0
enum定数のモック mockSingleton 5.22.0
@SuppressStaticInitializationFor mockito.suppress.clinit 5.24.0
privateメソッドのスタブ なし –

spyStaticは、Mockitoのjavadocでは「Since 5.23.0」と書かれていますが、v5.23.0タグのソースにはこのメソッドが無く、変更履歴でも5.24.0の項目に載っています。5.24.0以上を指定してください。

mockStatic・mockConstructionによる移行例

JUnit 5.13.4とmockito-core 5.24.0を使った例です。静的メソッドは前章と同じTaxTableを使い、コンストラクタはClock2を生成するGreeterを題材にしています。@PrepareForTestも専用Runnerも要りません。

@Test void mockStaticScope() {
    try (MockedStatic<TaxTable> tax = mockStatic(TaxTable.class)) {
        tax.when(() -> TaxTable.rateOf("food")).thenReturn(8);
        assertEquals(1080, new PriceService().priceWithTax("food", 1000));
        tax.verify(() -> TaxTable.rateOf("food"));
    }
    assertThrows(IllegalStateException.class, () -> TaxTable.rateOf("food"));
}

@Test void constructionMock() {
    try (MockedConstruction<Clock2> c = mockConstruction(Clock2.class,
            (mock, ctx) -> when(mock.hour()).thenReturn(9))) {
        assertEquals("おはよう", new Greeter().greet());
        assertEquals(1, c.constructed().size());
    }
}

この例では、モックは作成したスレッド上で、try-with-resourcesを抜けるまで有効です。同じブロック内で起動した別スレッドには適用されません。最後の assertThrows のとおり、ブロックを抜けると本物の rateOf() に戻ります。PowerMockのようにクラスを専用クラスローダで読み直すこともありません。

依存はmockito-coreだけです。Mockito 3.4〜4系で必要だったmockito-inlineは、5.0.0でinline mock makerが既定になって不要になり、Maven Centralの公開も5.2.0(2023年3月)で止まっています。

spyStatic・mockSingleton・静的初期化子の抑止

5.22.0以降に入った3つの機能です。いずれもJDK 25で実行して確かめました。

@Test void spyStaticKeepsRealMethods() {
    try (MockedStatic<TaxTable> tax = spyStatic(TaxTable.class)) {
        assertThrows(IllegalStateException.class, () -> TaxTable.rateOf("x"));
        tax.when(() -> TaxTable.rateOf("food")).thenReturn(8);
        assertEquals(8, TaxTable.rateOf("food"));
    }
}

@Test void enumSingleton() {
    try (MockedSingleton<Region> r = mockSingleton(Region.TOKYO)) {
        when(Region.TOKYO.shippingFee()).thenReturn(0);
        assertEquals(0, Region.TOKYO.shippingFee());
        assertEquals(800, Region.OSAKA.shippingFee());
    }
    assertEquals(500, Region.TOKYO.shippingFee());
}

spyStaticはスタブしたメソッド以外で本物を呼びます。mockSingletonは指定したenum定数(ここではTOKYO)だけをモックにし、OSAKAは本物のままです。

静的初期化ブロックでDB接続などを行うクラスは、PowerMockでは @SuppressStaticInitializationFor で初期化を止めていました。5.24.0ではシステムプロパティ mockito.suppress.clinit に完全修飾クラス名(または末尾 .* のパッケージ)を書くと、静的フィールドが既定値のまま読み込まれます。ただしPR #3801が説明するとおり、Mockitoを -javaagent として起動したときだけ効きます。エージェント無しでプロパティだけを渡したテストは、警告も出ずに ExceptionInInitializerError で落ちました。効果はJVM全体にかかるので、テストクラスごとに初期化の有無を変えることはできません。

JDK 21以降の動的アタッチ警告を避ける-javaagent設定

Mockito 5はinline mock makerを使うため、起動時にJVMへ自分をアタッチします。JEP 451によりJDK 21以降はこの動的ロードに警告が出るようになり、JDK 25で何も設定しないと次のメッセージが出ました(テスト自体は成功します)。

Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation
WARNING: Dynamic loading of agents will be disallowed by default in a future release

Mockitoのjavadoc(0.3節)の方法で、mockito-coreのjarを -javaagent に渡すと警告は消えました。Mavenではdependency pluginの properties ゴールでjarのパスを変数にします。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <executions>
    <execution><goals><goal>properties</goal></goals></execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <argLine>-javaagent:${org.mockito:mockito-core:jar} -Dmockito.suppress.clinit=demo.HeavyConfig</argLine>
  </configuration>
</plugin>

静的初期化子の抑止を使わないなら、-Dmockito.suppress.clinit の部分は不要です。

PowerMockを残す条件とOpenRewriteによる自動移行

PowerMockを残すのが合理的なのは、privateメソッドをスタブしているテストが多数あり、すぐには書き直せない場合です。その場合はMockitoを3.x系に固定してPowerMockのテストをJUnit 4のまま残し(JUnit Vintageを使えばJupiterのテストと同じビルドで実行できます)、JDKを上げるたびに --add-opens を足す運用を受け入れることになります。静的メソッド・コンストラクタ・final・enum・静的初期化子が用途なら、Mockito 5.24への移行を優先します。移行前に、別スレッドから呼ばれる静的メソッドが無いか(MockedStaticは作成したスレッドでしか効きません)と、nativeメソッドを差し替えていないかを確認してください。OpenRewriteのレシピ説明によれば、PowerMockは独自のMockMakerを登録するため、Mockitoのstaticモックやコンストラクタのモックと同じクラスパスに置けません。移行はモジュール単位でPowerMockを外す形になります。

機械的な置き換えには、OpenRewriteのレシピReplace PowerMock with raw Mockito(org.openrewrite.java.testing.mockito.ReplacePowerMockito)が使えます。注意したいのは、privateメンバーをモック・スタブ・検証しているテストを変換せず無効化する仕様である点です。対象のテストは本文がコメントアウトされ、@Disabled か @Ignore が付き、PowerMockTestsDisabled というデータテーブルに一覧されます。レシピを流したあとは、この一覧のテストをpublicな入口から検証する形へ書き直すまでが移行作業です。レシピはModerne Source Available Licenseで提供されているため、社内で使う前にライセンス条件も確認してください。移行後に網羅率が落ちていないかは、テストカバレッジのC0/C1/C2網羅率で確かめられます。

テスト資産が大きく、JDK・Mockito・JUnitの更新をCIで段階的に進めたい場合は、DevOps・CI/CD導入支援でパイプラインの設計から支援しています。

よくある質問

PowerMockitoの読み方は?

「パワーモッキート」と読みます。Mockito(モッキート)にPowerを冠した名前で、ライブラリ本体はPowerMock、そのMockito向けAPIがPowerMockitoです。

PowerMockはJUnit 5で使えますか?

JUnit 5のJupiter APIには対応していません。PowerMockはJUnit 4のRunner(@RunWith(PowerMockRunner.class))に依存しており、Jupiter用の拡張はありません。ただしJUnit 4のテストはJUnit Vintageエンジン経由でJUnit Platform上でも実行でき、junit-vintage-engine 5.13.4とjunit-jupiter 5.13.4を並べた構成では、PowerMockのテスト4件とJupiterのテストが同じビルドで通りました。新しく書くテストは、静的メソッドやコンストラクタのMock化をMockitoの mockStatic()・mockConstruction() で書きます。JUnit 5でのMockito設定の例はCDI-UnitとJUnit 5のテスト設定でも扱っています。

mockito-inlineとPowerMockitoの違いは?

mockito-inlineはMockitoのinline mock makerを有効にする公式アーティファクトで、静的メソッドとfinalのMock化をMockitoのAPIだけで行えます。Mockito 5.0.0以降はmockito-coreの既定になったため、追加は不要です。PowerMockitoは独自クラスローダでprivateまで差し替えられます。対応範囲はMockito 2〜3系と、JUnit 4・TestNGです。

@PrepareForTestを書いたのにモックが効かないのはなぜですか?

確認したいのは指定するクラスです。java.*のシステムクラスとwhenNewは、モック対象のクラスではなく呼び出し側・newを実行する側のクラスを指定します。取り違えると本物のメソッドが走り、verifyが「Wanted but not invoked」で失敗します。

PowerMockを使うとJaCoCoのカバレッジが計測されないのはなぜですか?

@PrepareForTestの対象クラスがPowerMockのクラスローダで読み直され、JaCoCoエージェントが行う実行時の書き換えが反映されないためです。JaCoCoのオフラインインストルメンテーションの説明は、オフライン方式が必要になる場面として「動的にクラスファイルを変換する他のエージェントとの衝突」を挙げています。回避策はオフライン方式への切り替えか、Mockitoの mockStatic() への移行です。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の不正アクセス:38.5万件の対象確認と窓口、停止中の手続き
  2. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  3. 2026.10.31 テックブログ 無印良品の情報漏洩:MUJIアプリ最大2,726万件の対象確認となりすましメール対策
  4. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  5. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処

RELATED POSTS 関連記事

目次