Javaでprivateメソッドのテストとそのメソッドをパーシャルモックする方法

## 概要
SpringBootでテスト書いてたらprivateメソッドのテストのやり方と、privateメソッドのモックのやり方がわからず詰まったのでまとめる。 Mockitoだけではprivateメソッドのモックができないようなので、PowerMockを併用する。

## 環境
- Java 8
- SpringBoot 1.5.7.RELEASE

## テスト対象クラス
テスト対象クラスを適当に用意する。`HelloService`クラスとする。

```java
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`クラスが依存するクラスを適当に作る。

```java
public class HelloRepository {

    public String getData() {
        return "HelloRepository.getData";
    }
}
```

### 解説
- `methodPrivate`メソッドはprivateメソッド。[privateメソッドのテストの章](#privateメソッドのテスト)でこのメソッドのテスト方法について解説する。
- `methodPublic`メソッドはpublicメソッドで、中でprivateメソッドを呼んでいる。[privateメソッドをパーシャルモックする方法の章](#privateメソッドをパーシャルモックする)で同じクラス内のprivateメソッドをパーシャルモック化する方法について解説する。
- `getFromRepo` は依存しているHelloRepositoryのメソッドを呼んでいる。[Mockitoのモックと併用するの章](#mockitoのモックと併用する)で、これまでのコードと共存する形でテストしてみる。

## privateメソッドのテスト
ここでは、上記テスト対象クラス`HelloService`クラスのprivateメソッドである`methodPrivate`メソッドのテスト方法について解説する。

### やってみる
```java
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')
}
```

テストを書く

```java
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`メソッドのテストを追加する。

```java
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を使おうとしたときに、やり方がガラッと変わったら混乱しそうだと思ったけど、同じ感じの書き方で書けそうで安心。

## 参考
- [MockPrivate · powermock/powermock Wiki · GitHub](https://github.com/powermock/powermock/wiki/MockPrivate)
- [Java | mockitoで単体テストをMockする - Tbpgr Blog](http://tbpgr.hatenablog.com/entry/20130506/1367849586)
- [Mockitoの新機能を使ったモダンげな使い方 - 愛と勇気と缶ビール](http://zentoo.hatenablog.com/entry/2015/12/24/204333)