きり丸の技術日記

技術検証したり、資格等をここに残していきます。

JavaのStaticメソッドをMockitoでモックする(ネストしたメソッドも値を返すようにする)

Javaのオープンチャットに「staticメソッドをMockitoでモックして値を返したい。NestするとNullが返るので、それを回避したい」という要望があったので、素振りしました。

環境

SpringBootStarterTestを基本としています。

  • Java
    • 17
  • org.springframework.boot:spring-boot-starter-test
    • 2.6.4
  • (org.mockito:mockit-core)
    • 4.6.0
  • org.mockito:mockito-inline
    • 4.6.1

下準備

Mockitoでstaticメソッドをモックにするには、mockito-inlineが必要なため、gradleの依存関係に含めます。SpringBootStarterTestにはmockito-coreが含まれていますので、バージョン違いで怪しい動きをする場合以外は不要です。

  • testImplementation 'org.mockito:mockito-inline:4.6.1'

対象コード

次のgetFirstメソッドの戻り値を変更することを目的とします。また、getFirstに依存しているgetSecond, getThirdの戻り値がnullではなく、依存しているgetFirstの戻り値であることが期待値です。

public class Target {
  public static String getFirst() {
    return "1";
  }
  public static String getSecond() {
    return getFirst();
  }
  public static String getThird() {
    return getSecond();
  }
}

対応

try-with-resources区でMockito.mockStaticを囲むと、そのブロック内の挙動が書き換わります。Mockito.mockStatic第1引数にモック対象のクラスを渡し、第2引数にどのようにモックにするかという挙動を指定します。

今回はNestしたメソッドの挙動も書き換えたいため、Mockito.CALLS_REAL_METHODSを渡します。Mockitoではwhenでメソッドの挙動を書き換えますが、デフォルトだとwhenメソッドで指定したメソッドのみしか書き換わりません。getFirstメソッドだけでなく、getSecondメソッドも書き換える必要があります。

Mockito.CALLS_REAL_METHODSパラメータを渡す場合は元のメソッドを呼ぶように書き換わります。この状態でgetFirstメソッドを書き換えることにより、getSecondメソッドがgetFirstメソッドを呼ぶようになるため、構造が深くなっても期待する値を返却してくれます。

// モック前
assertThat(Target.getFirst()).isEqualTo("1");
assertThat(Target.getSecond()).isEqualTo("1");
// モック中
try (var mocked = Mockito.mockStatic(Target.class, Mockito.CALLS_REAL_METHODS)) {
  // getFirstメソッドの戻り値が1から123に変更される
  mocked.when(() -> Target.getFirst()).thenReturn("123");

  assertThat(Target.getFirst()).isEqualTo("123");
  assertThat(Target.getSecond()).isEqualTo("123");
}
// モック後
assertThat(Target.getFirst()).isEqualTo("1");
assertThat(Target.getSecond()).isEqualTo("1");

第二引数を渡さないときの挙動

// モック前
assertThat(Target.getFirst()).isEqualTo("1");
assertThat(Target.getSecond()).isEqualTo("1");
// モック中
try (var mocked = Mockito.mockStatic(Target.class)) {
  // getFirstメソッドの戻り値が1から123に変更される
  mocked.when(() -> Target.getFirst()).thenReturn("123");

  assertThat(Target.getFirst()).isEqualTo("123");
  // whenでgetSecondを指定していないため、モック状態のnullが返却される
  assertThat(Target.getSecond()).isEqualTo(null); 
}
// モック後
assertThat(Target.getFirst()).isEqualTo("1");
assertThat(Target.getSecond()).isEqualTo("1");

ソースコード

終わりに

正直なところ、staticメソッドを書き換えるパターンというのは悪手であることが多いので、私はあまりやらないです。DIも実質的にはstaticメソッドみたいなところはあるのですが、差し替えやすいという点においてDIの方が使いやすいです。

Mockitoの第2引数にMockito.CALLS_REAL_METHODSを渡すことにより、パーシャルモックをしやすいというのは非常に自分の中での勉強になりました。パーシャルモックはあまり好まれないようなのですが、デメリットがイマイチ理解できていないため、個人的には今後のプロジェクトでは積極的に使用していきたいです。