はじめに
今回は「mockito を使ったコンストラクターを mock 化する方法」について記載します。
Java でユニットテストを記述する際に利用するモックライブラリ mockito の使い方のひとつの説明です。
以前、以下で「mockito を使った static メソッドを mock 化する方法」について書きましたが、
その派生、コンストラクターを mock 化する場合の内容になります。
コンストラクタを mock 化する際のモチベーションについて
この機能を使うモチベーション、つまり使いどころとしては、
メソッド内でクラスインスタンスを生成しているものがあり、
具体的な例をコードで記述すると、以下のようなメソッドを想定しています。
public Object method(String value) {
var o = new SomeClass(value);
return o.doSomething();
}
mockito によるコンストラクタの mock 化
コンストラクタを mock 化する方法として powermock を使う方法があると思います。
前述した前回の記事でも触れましたが、powermock は JUnit5 対応や対応の検討もされていないため、
mockito を使った方法があるのでそれを使います。
mockito の mockConstruction というメソッドを使うと、
そのスコープ中は該当するクラスのインスタンスは自動的に mock に指し変わった状態となるため、
自由にふるまいを定義してテストに使うことができます。
Mockito 3.5.0 以降であれば、mockito によるコンストラクターの mock 化が可能になっています。
(出展として、草案のようなPR や機能マージされたPRはありましたが、Github のリリースノートなどに機能追加されたことの説明などをみつけらませんでした。)
使い方の確認
実際の実装・動作確認した方法について触れていきます。
開発環境
- OS: Windows 10 Pro
- IDE: Intellij IDEA 2021.2 (Ultimate Edition)
- JDK: openjdk-16.0.2
- ビルドツール: gradle(7.0)
JDKバージョン、gradle のバージョンは古すぎなければほかのバージョンでも問題ないと思います。
私の環境では(たまたま)このバージョンを使っていました。
実装方法
mockito モジュールをビルドツールの設定に追加
gradle を使っている場合、build.gradle の依存関係に以下を追記します。
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0'
testImplementation 'org.mockito:mockito-inline:3.12.4'
testImplementation 'org.assertj:assertj-core:3.20.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0'
}
test {
useJUnitPlatform()
}
モジュールのバージョンは記載時点(2021/09)の最新版を使用しています。
実装例
コンストラクターを使っているメソッドのテストの実装例を記載します。
以下のようなクラスをテストするとします*2。
public class UseConstructorSample {
public String useMethod(String arg1, String arg2) {
ConstructorSample sample = new ConstructorSample(arg1);
return sample.method(arg2);
}
public static class ConstructorSample {
private final String value;
public ConstructorSample(String value) {
this.value = value;
}
public String method(String arg) {
return String.format("field=[%s], method=[%s]", value, arg);
}
}
}
ConstructorSample を mock 化し、UseConstructorSample の useMethod メソッドをテストすることを考えます。
実際にテストするコードは以下のように記述できます。
class UseConstructorSampleTest {
@Test
final void test() {
final String expected = "mockedValue";
try(MockedConstruction<ConstructorSample> mocked = mockConstruction(ConstructorSample.class,
(mock, ctx) -> doReturn(expected).when(mock).method(anyString()))
) {
UseConstructorSample target = new UseConstructorSample();
String actual = target.useMethod("value1", "value2");
assertThat(actual).isEqualTo(expected);
}
}
}
mockConstruction() メソッドの第一引数で mock 化したい対象のクラスを指定し、
第二引数で mock の振る舞いをラムダ式で定義しています。
第二引数で使っている mock が mock 化したい対象のクラス(今回だと ConstructorSample)になっているのに注意してください。
mockConstruction() が返す MockConstruction<T> のインスタンスは mock の制御をするためのインスタンスで、mock 自体ではないようです。
定義した mock の振る舞いは、try 文で囲われた領域で定義した通りの振る舞いを行います。
(mock, ctx) -> doReturn(expected).when(mock).method(anyString())
※doReturn、when を使って mock の振る舞いを定義。今回は「すべての文字列を受け取った時に expected を返す」振る舞いをする。
try 文で囲っているのは、MockConstruction を自動的に Close するためです。
インスタンスを Close されるまでが mockConstruction() で定義した mock の有効期間になります。
try 文を使わない場合は、最後に close() を呼ぶ必要があります。
MockedConstruction<ConstructorSample> mocked = mockConstruction(ConstructorSample.class);
...
mocked.close();
最後に
コンストラクターを mock 化する方法は、あまり使うケースが多くないとは思うのですが、
調べてみると mockito を使った方法についての情報になかなかたどりつかなかったり、
自分な必要な情報単位で巡り合えなかったので、このようにまとめるようにしました。