JUnit + MockitoでMock化した処理の中でもう一つ何か処理を行わせる

## はじめに
[ひとりJUnitアドベントカレンダー](https://qiita.com/advent-calendar/2022/junit)10日目の記事です。
端的に言おうとしすぎてタイトルが意味不明な文章になってる。

## 使用バージョン

```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
```

## こんなことはないと思いますが
テスタビリティ低いし違和感あるので思いとどまるべきコードなんだろうなと思いつつも、
書いてしまったor書かざるを得ない場面を想定します。

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

  @NonNull
  private MogeService mogeService;

  public int execute() {
    Student student = new Student();
    // studentの内容を更新
    mogeService.update(student);

    if ("太郎".equals(student.getName())) {
      return 1;
    }
    return 0;
  }
}
```

```java:MogeService
@Service
public class MogeService {

  public void update(Student student) {
    if (new Random().nextBoolean()) {
      student.setName("太郎");
    } else {
      student.setName("次郎");
    }
  }
}
```

`HogeService`の中で`Student`インスタンスを生成し、`MogeService`に渡すと
`MogeService`側でインスタンスの内容が変更され、
その後は更新した`Student`インスタンスのメンバの値を元に分岐を行う処理です。

言うまでもないですが、`MogeService`には`Student`インスタンスの参照が渡されるので
`MogeService`から`HogeService`に`Student`インスタンスを返却せずとも
`HogeService`では内容変更後のインスタンスを扱うことができます。

ただ、`HogeService`上では`MogeService`側でどんな変更が行われるかわかりませんし
不完全な状態の`Student`が一時的に生まれるという点でリスクもあるため、微妙です。
そもそもこのコードであれば`MogeService`側で`Student`を生成してreturnする方が
よっぽど綺麗ですが、まあ更に別の処理があったり複雑な事情があったりするのでしょう。

これのテストコードを書いてみます。

## とりあえず書けはしたものの
Mockitoを使って`MogeService`をmock化してテストしますが、
`MogeService#update()`はvoidなので特に挙動を指定せずに書いてみます。

```java:HogeServiceTest
@ExtendWith(MockitoExtension.class)
class HogeServiceTest {

  @InjectMocks
  private HogeService hogeService;

  @Mock
  private MogeService mogeService;

  @Test
  void テスト() {
    int actual = hogeService.execute();
    assertThat(actual, is(0));
  }
}
```

とりあえず1ケースは書けました。
`MogeService#update()`はMock化した後に特に挙動を指定していないため、
本当のメソッドでは"太郎"か"次郎"のいずれかがsetされるはずですが、
今回のテストでは特に何もsetされず後続処理に進みます。

本記事が載るアドベントカレンダーの前日までの記事で、Mockitoの使い方を見てきましたが
触れたのは「特定の戻り値を返す」「例外を投げる」という挙動の設定方法のみでした。

`HogeService`を分岐網羅するテストを書くためには、
`MogeService#execute()`が"太郎"をsetしてくれないといけません。
しかし当該処理はvoidなので、今までの設定方法ではうまくいきません。
さあどうすればよいでしょうか。

## 答え：Answer
激寒ギャグになってしまいましたが、答えは`Answer`です。

```java:HogeServiceTest.java
  @Test
  void テスト() {
    doAnswer(new Answer() {
      @Override
      public Object answer(InvocationOnMock invocation) {
        invocation.getArgument(0, Student.class).setName("太郎");
        return null;
      }
    }).when(mogeService).update(any());

    int actual = hogeService.execute();
    assertThat(actual, is(1));
  }
```

`doReturn()`, `doThrow()`に続き`doAnswer()`です。
要するにmock化されたメソッドの挙動を自分で自由に定義しちゃおうよというもので、
今回の場合はmock化されたメソッドの第一引数を取得して、"太郎"をsetしてみました。

ゴチャゴチャしていてわかりにくいですが、
`Answer`インターフェースはメソッドを一つしか持たないため、ラムダ式で書けます。

```java:HogeServiceTest.java
  @Test
  void テスト() {
    doAnswer(invocation -> {
      invocation.getArgument(0, Student.class).setName("太郎");
      return null;
    }).when(mogeService).update(any());

    int actual = hogeService.execute();
    assertThat(actual, is(1));
  }
```

不慣れな場合は焼け石に水というかむしろ難解かもですが・・・

再利用のタイミングが多いのであればクラスとして切ってしまうのもアリだと思います。

```java:HogeServiceTest.java
  @AllArgsConstructor
  private class SetNameAnswer implements Answer {

    private String name;

    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
      invocation.getArgument(0, Student.class).setName(name);
      return null;
    }
  }

  @Test
  void テスト() {
    doAnswer(new SetNameAnswer("太郎")).when(mogeService).update(any());

    int actual = hogeService.execute();
    assertThat(actual, is(1));
  }
}
```
テスト側の処理はこれが一番シンプルですね。

## 本当に使えるのかこれ
一応何回か実プロジェクトで(それはむしろ意識的に)使っています、が
多くの場合、まずは実装をテスト容易な形にできないか検討してみても損はないと思います。

一応前述のケース以外の使い道としては、
引数の値に応じてreturnする処理を変えるためという目的も考えられなくはないですが、
テストの中にロジックを持たせすぎるとそのロジックが誤っていた場合に死にます。
`thenReturn()`などをチェーンさせていくことで1回目、2回目と挙動を指定できるので
`Anwer`内で引数を取得して分岐させるよりも、素直に複数回挙動設定しましょう。

ということで、使えるともしかしたら役に立つ場面が稀にあるかもしれないですが
基本的には使う必要がない(はずの)、`Answer`のご紹介でした。