概要
SpringBootでテスト書いてたらprivateメソッドのテストのやり方と、privateメソッドのモックのやり方がわからず詰まったのでまとめる。 Mockitoだけではprivateメソッドのモックができないようなので、PowerMockを併用する。
環境
- Java 8
- SpringBoot 1.5.7.RELEASE
テスト対象クラス
テスト対象クラスを適当に用意する。HelloService
クラスとする。
import org.springframework.beans.factory.annotation.Autowired;
public class HelloService {
@Autowired
private HelloRepository repository;
public String getFromRepo() {
return repository.getData();
}
public String methodPublic(String arg) {
return "method public: " + methodPrivate(arg);
}
private String methodPrivate(String arg) {
return "method private, arg: " + arg;
}
}
HelloService
クラスが依存するクラスを適当に作る。
public class HelloRepository {
public String getData() {
return "HelloRepository.getData";
}
}
解説
-
methodPrivate
メソッドはprivateメソッド。privateメソッドのテストの章でこのメソッドのテスト方法について解説する。 -
methodPublic
メソッドはpublicメソッドで、中でprivateメソッドを呼んでいる。privateメソッドをパーシャルモックする方法の章で同じクラス内のprivateメソッドをパーシャルモック化する方法について解説する。 -
getFromRepo
は依存しているHelloRepositoryのメソッドを呼んでいる。Mockitoのモックと併用するの章で、これまでのコードと共存する形でテストしてみる。
privateメソッドのテスト
ここでは、上記テスト対象クラスHelloService
クラスのprivateメソッドであるmethodPrivate
メソッドのテスト方法について解説する。
やってみる
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class HelloServiceTest {
private HelloService service;
@Before
public void setUp() {
service = new HelloService();
}
@Test
public void methodPrivateのテスト() throws Exception {
Method method = HelloService.class.getDeclaredMethod("methodPrivate", String.class);
method.setAccessible(true);
String actual = (String)method.invoke(service, "hoge");
assertThat(actual, is("method private, arg: hoge"));
}
}
解説
-
テスト対象クラス.class.getDeclaredMethod(テスト対象メソッド名、引数の型)
とすると、テスト対象クラスのメソッドインスタンスが取得できる - method.setAccessible(true)とするとprivateメソッドでも呼べるようになる
- method.invoke(引数)でそのメソッドが呼べる。返り値がObjectになるので、キャストする。
-
Unchecked cast
って警告がでたら、上の行でに@SuppressWarnings("unchecked")
をつけると消える
-
privateメソッドをパーシャルモックする
ここでは、上記テスト対象クラスHelloService
クラスのmethodPublic
メソッドをテストするにあたって、中で呼んでいるprivateメソッドmethodPrivate
をパーシャルモック化する方法について解説する
やってみる
PowerMockを使うのでbuild.gradleに以下を追加(maven使っている場合は適当に読み替えてください)
dependencies {
testCompile('org.powermock:powermock-module-junit4:1.6.2')
testCompile('org.powermock:powermock-api-mockito:1.7.3')
}
テストを書く
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {
private HelloService service;
@Before
public void setUp() {
service = spy(new HelloService());
}
@Test
public void methodPublicのテスト() throws Exception {
when(service, "methodPrivate", "fuga").thenReturn("hoge");
String actual = service.methodPublic("fuga");
verifyPrivate(service, times(1)).invoke("methodPrivate", "fuga");
assertThat(actual, is("method public: " + "hoge"));
}
}
解説
- クラスにアノテーションを2つ追加。
@RunWith(PowerMockRunner.class)
と@PrepareForTest(テスト対象.class)
- パーシャルモックを作るために、
setUp()
の中でspy(テスト対象クラスのインスタンス)
を呼んでいる。ここでMockitoのspyではなく、PowerMockのspy(org.powermock.api.mockito.PowerMockito.spy
)にするのがポイント -
when()
についても同様で、PowerMock のwhenを使う。org.powermock.api.mockito.PowerMockito.when
を使っている。 - これでprivateだろうとpublicだろうと気にせずパーシャルモックが作れる
- 呼ばれたことの検証には、
verifyPrivate
を使う。 - whenやverifyの使い勝手はMockitoと同じ感じっぽい
Mockitoのモックと併用する
getFromRepo
メソッドのテストを追加する。
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {
@Mock
private HelloRepository repository;
@InjectMocks
private HelloService service;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
service = spy(service);
}
@Test
public void getFromRepoのテスト() {
when(repository.getData()).thenReturn("hoge");
assertThat(service.getFromRepo(), is("hoge"));
verify(service, times(1)).getFromRepo();
}
@Test
public void methodPublicのテスト() throws Exception { // 上と同じ }
@Test
public void methodPrivateのテスト() throws Exception { // 上と同じ }
}
解説
- 依存している
HelloRepository
をモックにしている。 - モックにしたいフィールドに
@Mock
アノテーションをつけて、モックを注入したいフィールドに@InjectMocks
アノテーションをつける -
setUp()
の中でMockitoAnnotations.initMocks(this);
を呼んであげると、よしなにモックを注入してくれるみたい - 上記のPowerMockと共存するか心配だったけど、単純にinitMocksの後に
service = spy(service);
ってやってあげたら良い感じに動いた。
所感
PowerMockが便利だった。
privateじゃないメソッドを(今回でいうHelloRepository的なやつ)モックする時はMockitoを使っていて、@Mock
とか@InjectMocks
便利だなーと思っていた。
privateメソッドをモックするのにPowerMockを使おうとしたときに、やり方がガラッと変わったら混乱しそうだと思ったけど、同じ感じの書き方で書けそうで安心。