JUnit + Mockito小ネタ 呼出ごとにMockの挙動を変える/呼出回数の様々な検証方法

## はじめに
[ひとりJUnitアドベントカレンダー](https://qiita.com/advent-calendar/2022/junit)9日目の記事です。
今回は[二日前](https://qiita.com/unmelt/items/68952a2e7a6aef387350)と[一日前](https://qiita.com/unmelt/items/bb11e25bc6de7b6fe787)の記事に入れ漏れたMockitoの小ネタです。

## 使用バージョン

```xml:pom.xml
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
```

```console
$ mvn dependency:tree | grep mockito
[INFO] |  +- org.mockito:mockito-junit-jupiter:jar:3.1.0:test
[INFO] |  +- org.mockito:mockito-core:jar:3.1.0:test
```

## 前提
以下のようなクラス構成を想定します。

```java:HogeService.java
@Service
@RequiredArgsConstructor
public class HogeService {

  @NonNull
  private MogeService mogeService;

  public void execute() {
    System.out.println(mogeService.returnStr());
    System.out.println(mogeService.returnStr());
  }
}
```
```java:MogeService.java
@Service
public class MogeService {

  public String returnStr() {
    return new Random().nextBoolean() ? "foo" : "bar";
  }
}
```

## 呼出ごとにMockの挙動を変える
`HogeService`は`MogeService`の`returnStr()`を二回呼んでいます。
テストの中で`MogeService`をmock化したとして、
呼び出された`returnStr()`の返却値を設定するためには以下のように書きます。

```java
    when(mogeService.returnStr()).thenReturn("foo");
```

この書き方をすると、何回呼び出されたとしても常に`"foo"`を返却します。
では、一回目は`"foo"`を、二回目は`"bar"`を返すような設定としたい場合はどうでしょう。
または、一回目は`"foo"`を返却、二回目は例外を投げさせたい場合はどうでしょうか。

以下です。
```java
    // foo -> bar(チェーン)
    when(mogeService.returnStr()).thenReturn("foo").thenReturn("bar");
    // foo -> bar(可変長引数利用)
    when(mogeService.returnStr()).thenReturn("foo", "bar");
    // foo -> 例外
    when(mogeService.returnStr()).thenReturn("foo").thenThrow(new NullPointerException());
```

単純にチェーンさせるだけです。もしくは可変長引数なので複数渡す形でも。
ちなみにこの設定で三回目を呼ぶと、上なら`"bar"`、下ならぬるぽになります。
最後に設定した値が残存するようなイメージですね。

## `thenThrow()`の指定方法
上の話で思い出したので一つ。
mock呼出時に例外を発生させる`thenThrow()`あるいは`doThrow()`ですが、
インスタンスを生成して引数に渡す方法の他に、例外のクラスを渡す方法もあります。

```java
    when(mogeService.returnStr()).thenThrow(new NullPointerException());
    when(mogeService.returnStr()).thenThrow(NullPointerException.class);
```

インスタンス化するのが面倒な例外とかだと使い所ありそうですね。
```java
    when(mogeService.returnStr()).thenThrow(WebServiceIOException.class);
```

## 呼び出し回数の検証方法
mock化したクラスのメソッドが呼ばれた回数は以下の記述で検証ができます。

```java
    verify(mogeService, times(2)).returnStr();
```

今回想定するテスト対象は以下なので、上記テストは通ります。
```java:HogeService#execute
  public void execute() {
    System.out.println(mogeService.returnStr());
    System.out.println(mogeService.returnStr());
  }
```

回数を厳密に指定する以外にも、以下のような検証も可能です。
```java
    // 最低1回は呼び出されること。引数の数字を変えれば最低2回、3回なども可能
    verify(mogeService, atLeast(1)).returnStr();
    // 最低1回の検証は専用のメソッドを使ってもOK
    verify(mogeService, atLeastOnce()).returnStr();
    // 最大3回まで呼び出されること
    verify(mogeService, atMost(3)).returnStr();
    // 最大でも1回まで呼び出されること。今回のケースだと落ちる
    verify(mogeService, atMostOnce()).returnStr();
```

また、以下のようなメソッド単位ではなくクラス単位の検証もあります。
```java
    // mogeServiceの全てのメソッドが一度も呼ばれていないこと
    verifyNoInteractions(mogeService);
    // mogeServiceのメソッドがこれ以上呼ばれていないこと
    verifyNoMoreInteractions(mogeService);
```

`verifyNoInteractions()`はまあ名前の通り、何も呼ばれていないよねという検証ですが
`verifyNoMoreInteractions()`は若干複雑で、
「すべての呼び出しをちゃんと検証しきれているか？」を検証するメソッドです。

```java
  @Test
  void テスト() {
    hogeService.execute();
    // ここでは落ちる
    verifyNoMoreInteractions(mogeService);
    // MogeServiceのメソッドが呼ばれていることを検証
    // hogeService.execute()はreturnStr()以外は呼ばないので、これだけで全て検証完了
    verify(mogeService, times(2)).returnStr();
    // 検証完了しているため、ここでは通る
    verifyNoMoreInteractions(mogeService);
  }
```

メソッドが多いクラスをmock化した場合、「検証していないものがないこと」の検証は
自分で書こうとするとなかなか大変なものです。
「どのメソッドも一度も呼ばれていないこと」も同じで、
全てのメソッドを`verify(mock, times(0))`するのも馬鹿馬鹿しいですし、
せっかくライブラリ側が用意してくれているので、適切に使ってできるだけラクしたいですね。