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