SpringBootでJUnitでテストをするときの様々なメソッドをモックするときにMockitoとPowerMockの使い分けを整理してみました。
テストをする際に必要な設定
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile group: 'org.powermock', name: 'powermock-api-mockito', version: '1.6.6'
testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '1.6.6'
}
spring-boot-starter-testを入れるだけで、以下の二つのライブラリが使用可能になります。
バージョンはspring-boot-gradle-plugin:1.5.2.RELEASEのものです。
- junit-4.12.jar
- mockito-core-1.10.19.jar
これに、後ほど使用するpowermockを使用する設定を加えておきます。
説明に使うクラス
テストの対象となるクラス
@Component
public class TestTargetClass {
@Autowired
private TestSubClass testSubClass;
private String private_method() {
return "private_method called " + private_sub();
}
private String private_sub() {
return "private_sub";
}
public String public_method() {
return "public_method called " + public_sub();
}
public String public_sub() {
return "public_sub";
}
public String public_method_call_private_method() {
return "public_method called " + private_sub();
}
public static String static_method() {
return "static_method";
}
public String public_subclass_public_method() {
return "public_subclass_method called " + testSubClass.sub_public_method();
}
public String public_subclass_private_method() {
return "public_subclass_method called " + testSubClass.sub_public_method_call_private_method();
}
}
テストの対象となるサブクラス(親クラスからDIされるサブクラス)
@Component
public class TestSubClass {
public String public_field = "public_field";
private String private_field = "private_field";
private String sub_private_method() {
return "subclass_private_method has " + private_field;
}
public String sub_public_method() {
return "subclass_public_method has " + public_field;
}
public String sub_public_method_call_private_method() {
return "subclass_public_method called " + sub_private_method();
}
}
まずは基本のテストから
テストクラスはこんな感じでMockitoが使えます。
@RunWith(SpringRunner.class)
@SpringBootTest
public class RealTest {
@Autowired
private TestTargetClass instance;
@Test
public void テストメソッド() {
.....
}
}
publicメソッドの実際の挙動のテスト
書くまでもないですが、テストコードはこちら
@Test
public void publicメソッドを呼び出してテストできる() {
assertThat(public_method(), is("public_method called public_sub"));
}
privateメソッドの実際の挙動のテスト
リフレクションを使ってsetAccessibleを使えばOK
@Test
public void privateメソッドを呼び出してテストできる() throws Exception {
Method method = TestTargetClass.class.getDeclaredMethod("private_method");
method.setAccessible(true);
assertThat((String) method.invoke(instance), is("private_method called private_sub"));
}
staticメソッドの実際の挙動のテスト
書くまでもないですが、テストコードはこちら
@Test
public void staticメソッドを呼び出してテストできる() {
assertThat(TestTargetClass.static_method(), is("static_method"));
}
ここからが本題のモックを使ったテストです。
Mockitoだけでできるテスト
publicメソッドをモックする場合
このようなときにはMockito.mock()
を使用します。
@Test
public void publicメソッドをモックできる() {
TestTargetClass mockInstance = mock(TestTargetClass.class);
when(mockInstance.public_method()).thenReturn("mocked_public_method");
assertThat(mockInstance.public_method(), is("mocked_public_method"));
}
publicメソッドから呼ばれるサブのpublicメソッドをモックする場合
モックを行うメソッドpublic_sub()
以外のメソッドは実装通りに動いてほしいので、このようなときにはMockito.spy()
を使用します。
@Test
public void publicメソッドが呼ぶSubメソッドはSpyでモックできる() {
TestTargetClass mockInstance = spy(new TestTargetClass());
when(mockInstance.public_sub()).thenReturn("mocked_public_sub");
assertThat(mockInstance.public_method(), is("public_method called mocked_public_sub"));
}
PowerMockでできるテスト
PowerMockを使うときのテストクラスはこんな感じ
@RunWith(PowerMockRunner.class)
@PrepareForTest({TestTargetClass.class})
public class PowerMockTest {
@Test
public void テストメソッド() {
.....
}
}
privateメソッドのモック
PowerMockito.mock()
とPowerMockito.when()
を使います。またprivateメソッドを指定するためMemberMatcher.method()
を使ってレフレクションします。
@Test
public void privateメソッドはPowerMockitoでモックできる() throws Exception {
TestTargetClass mockInstance = PowerMockito.mock(TestTargetClass.class);
PowerMockito.when(mockInstance, MemberMatcher.method(TestTargetClass.class, "private_method"))
.withNoArguments().thenReturn("mocked_private_method");
// privateメソッドにアクセスするためにリフレクションを使う
Method method = TestTargetClass.class.getDeclaredMethod("private_method");
method.setAccessible(true);
assertThat((String) method.invoke(mockInstance), is("mocked_private_method"));
}
staticメソッドのモック
PowerMockito.mockStatic()
とPowerMockito.when()
を使います。
@Test
public void staticメソッドはPowerMockitoでモックできる()() {
PowerMockito.mockStatic(TestTargetClass.class);
PowerMockito.when(TestTargetClass.static_method()).thenReturn("mocked_static_method");
assertThat(TestTargetClass.static_method(), is("mocked_static_method"));
}
publicメソッドから呼ばれるサブのprivateメソッドをモックする場合
モックを行うメソッドprivate_sub()
以外のメソッドは実装通りに動いてほしいので、このようなときにはPowerMockito.spy()
とPowerMockito.when()
を使用します。またprivateメソッドを指定するためMemberMatcher.method()
を使ってレフレクションします。
@Test
public void publicメソッドから呼ばれるサブのprivateメソッドはspyでモックできる() throws Exception {
TestTargetClass mockInstance = PowerMockito.spy(new TestTargetClass());
PowerMockito.when(mockInstance, MemberMatcher.method(TestTargetClass.class, "private_sub"))
.withNoArguments().thenReturn("mocked_private_sub");
assertThat(mockInstance.public_method_call_private_method(),
is("public_method called mocked_private_sub"));
}
クラスでDIされているクラスをモックする場合
コントローラークラスでDIしているサービスクラスに対してモックを行う様なケースを想定しています
この時もPowerMockを使用します。
@SpringBootTest
アノテーションで@InjectMocks
で指定したクラスに対して中で指定している@Autowired
の対象クラスをDIします。
@Autowired
の対象クラスはクラス変数としてPowerMockito.spy()
します。
テストクラスはこんな感じ
@RunWith(PowerMockRunner.class)
// PowerMock対象のクラス
@PrepareForTest({TestSubClass.class})
@SpringBootTest
public class PowerMockSubclassTest {
@InjectMocks
private TestTargetClass instance;
private TestSubClass testSubClass = PowerMockito.spy(new TestSubClass());
@Test
public void テストメソッド() {
.....
}
}
クラス変数ではなく下のようにしてもモックはできないようです。 ... 多分
何か良い方法があったら是非コメントください。
@Test
public void テストメソッド() {
TestSubClass testSubClass = PowerMockito.spy(new TestSubClass());
}
サブクラスのpublicメソッドをモック
本当はpublicメソッドなのでテストクラスでサブクラスを宣言している箇所は
private TestSubClass testSubClass = PowerMockito.mock(TestSubClass.class);
でもよいのですが、同じソースファイル内でspy()を使用したものといっしょに使えないのでspy()の方で統一しておきます。
PowerMockito.when()
を使います。
@Test
public void サブクラスのpublicメソッドをモックできる() {
PowerMockito.when(testSubClass.sub_public_method()).thenReturn("mocked_subclass_public_method");
assertThat(instance.public_subclass_public_method(),
is("public_subclass_method called mocked_subclass_public_method"));
}
サブクラスのprivateメソッドをモック
PowerMockito.when()
を使います。またprivateメソッドを指定するためMemberMatcher.method()
を使ってレフレクションします。
@Test
public void サブクラスのprivateメソッドをモックできる() throws Exception {
PowerMockito.when(testSubClass, MemberMatcher.method(TestSubClass.class, "sub_private_method"))
.withNoArguments().thenReturn("mocked_sub_private_method");
assertThat(instance.public_subclass_private_method(), is(
"public_subclass_method called subclass_public_method called mocked_sub_private_method"));
}
サブクラスのpublicフィールドをモック
サブクラスのフィールドにモックをセットする場合はWhitebox.setInternalState()
を使います。
@Test
public void サブクラスのpublicフィールドをモックできる() {
Whitebox.setInternalState(testSubClass, "public_field", "mocked_public_field");
assertThat(instance.public_subclass_public_method(),
is("public_subclass_method called subclass_public_method has mocked_public_field"));
}
サブクラスのprivateフィールドをモック
サブクラスのフィールドにモックをセットする場合はWhitebox.setInternalState()
を使います。
publicフィールドにセットする場合と同じです。
@Test
public void サブクラスのprivateフィールドをモックできる() {
Whitebox.setInternalState(testSubClass, "private_field", "mocked_private_field");
assertThat(instance.public_subclass_private_method(), is(
"public_subclass_method called subclass_public_method called subclass_private_method has mocked_private_field"));
}
長文になりましたが、以上です。