JUnit + MockitoでDIしているサービスをMock化する

## はじめに
[ひとりJUnitアドベントカレンダー](https://qiita.com/advent-calendar/2022/junit)7日目の記事です。
いろいろあって放置しているうちに12月18日になってました。まだ7日目なのに。

## 使用バージョン

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

springbootの古さは目を瞑ってください。

## テスト対象クラスのロジックだけテストしたい
単体テストなのでテスト対象クラス単体だけテストしたいですね。

SpringbootにはDependency Injectionという仕組みがあります。
DIの説明は他の記事に譲りますが、テスト対象クラスに他のクラスが注入されていると
そのクラスをテストしようとした際、勝手にDIされて他クラスのロジックも通ってしまいます。
(もしくはDIに失敗してぬるぽになります)

注入されている側のロジックは無視、というか挙動を固定しておいて、
テスト対象クラスのロジックだけテストすることができたら、それはもううれしい。
ということで使えるのがMockitoです。

今回は以下のようなクラス構成で考えます。
```java:HogeService.java
@Service
@RequiredArgsConstructor
public class HogeService {

  @NonNull
  private final MogeService mogeService;

  public void execute() {
    System.out.println(mogeService.returnStr());
    mogeService.returnNothing();
    System.out.println("hoge executed.");
  }
}
```

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

  public String returnStr() {
    System.out.println("returnStr executed.");
    return "moge return";
  }

  public void returnNothing() {
    System.out.println("returnNothing executed.");
  }
}
```

`HogeService`はコンストラクタインジェクションで`MogeService`が注入されていて、
`MogeService`はStringを返すメソッドとvoidのメソッドをそれぞれ持っている形です。

`HogeService`をテストしようとした時、`MogeService`をMockにできれば嬉しいよね、
というのが今回の趣旨です。

## 手順1：`HogeSerivce`のテストクラスを作る
さて、まずは普通にテストクラスを作ります。

```java:HogeServiceTest
class HogeServiceTest {

  private HogeService hogeService;

  @Test
  void テスト1() {
    hogeService.execute();
  }
}
```
ここはただの下準備なので何も言うことはありません。
テストクラスを作ってテスト対象クラスを呼び出してみただけです。
ちなみに変数`hogeService`に何も代入していないため、今のところぬるぽで即落ちます。

## 手順2：`HogeService`のインスタンスを作る
次はぬるぽを解消しましょう。
`hogeService`にインスタンスを詰めてあげるだけです。

```java:HogeServiceTest
class HogeServiceTest {

  private HogeService hogeService;

  @BeforeEach
  private void setup() {
    hogeService = new HogeService(new MogeService());
  }

  @Test
  void テスト1() {
    hogeService.execute();
  }
}
```
`HogeService`はアノテーション`@RequiredArgsConstructor`を付与しているので、
`MogeService`のインスタンスを一つ受け取るコンストラクタをpublicで持っています。

よってnewした`MogeService`を渡してあげることでインスタンス化できますが、
これでは`execute()`を呼んだ時に`MogeService`の処理まで動いてしまいますね。

## 手順3：`MogeService`をmockにする
そこで、`@Mock`です。

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

  private HogeService hogeService;

  @Mock
  private MogeService mockedMogeService;

  @BeforeEach
  private void setup() {
    hogeService = new HogeService(mockedMogeService);
  }

  @Test
  void テスト1() {
    hogeService.execute();
  }
}
```

重要なポイントは以下の3点です。
* クラスに`@ExtendWith`で`MockitoExtention.class`を指定する
* モック化したいクラスに対して`@Mock`を付与する
* モック化したクラスをテスト対象クラスにinjectionする

`@Mock`を付与された`mogeService`はモックとなるため
`hogeService.execute()`を実行しても、実際の`MogeService`の処理は呼ばれません。


<details><summary>余談</summary><div>

コンストラクタインジェクションだからこうやって書いてたけど、

```java
hogeService = new HogeService(mockedMogeService);
```

仮にフィールドインジェクションだったらどうなるの？

```java
public class HogeService {

  @Autowired
  private MogeService mogeService;
```

そういう場合はアノテーションを使います。
というか、コンストラクタインジェクションでもこれで書けますし、
これを推奨している記事が多いイメージですが、なんとなく手癖が・・・
```java
  @InjectMocks
  private HogeService hogeService;
```

</div></details>

## 手順4：Mockの挙動を設定する
> 実行しても、実際の`MogeService`の処理は呼ばれません。

じゃあ何が呼ばれるんだよという話になるので、
以下の処理を呼んでいるテストの実行結果を見てみます。

```java:HogeService#execute
  public void execute() {
    System.out.println(mogeService.returnStr());
    mogeService.returnNothing();
    System.out.println("hoge executed.");
  }
```

```console
null
hoge executed.
```

1行目の通り、`mogeService`の`returnStr()`からの戻り値は`null`でした。
`returnNothing()`も元の処理ではsysoutが行われますが、今回は何も出力されません。
すなわち、戻り値ありのメソッドは`null`を返し、voidのメソッドは何もしなくなります。

ぬるぽは起きなくなりました、でもこのままでは意味がありません。
`mogeService.returnStr()`が空文字を返却するパターンも、
`mogeService.returnNothing()`が例外を投げるパターンも、
まだ何もテストしていないからです。こんなところで終わるわけにはいかない。

```java
  @Test
  void テスト1() {
    when(mogeService.returnStr()).thenReturn("mogemoge");
    // もしくは
    doReturn("mogemoge").when(mogeService).returnStr();
    hogeService.execute();
  }

  @Test
  void テスト2() {
    doThrow(new NullPointerException()).when(mogeService).returnNothing();
    // 引数なしの場合はwhen()から始めることはできない
    hogeService.execute();
  }
```

ので、mockの挙動を自分で設定してみます。
使っている`when`や`doThrow`は`Mockito`が提供しているメソッドで、
今回は`org.mockito.Mockito.*`をstaticインポートして使用しています。

覚えてしまえば直感的に使えるためわかりやすいですが、
戻り値の有無で書き方が変わることと、mock側メソッドの引数の扱いには注意が必要です。

まず戻り値の有無について。
戻り値があるメソッドは`when()`で囲んだ後、`thenReturn()`を生やす書き方と
`doReturn()`から始めて`when()`に繋げる書き方の2パターンが可能です。

戻り値のないvoidのメソッドについては、
先に`doThrow()`を書いておいて、そこから`when().method()`と続くのみで、
`when()`から始めることはできません。

```java
    // whenから始める(戻り値あり)
    when(mogeService.returnStr()).thenReturn("mogemoge");
    // whenから始める(戻り値なし)：これはコンパイルエラーになる
    when(mogeService.returnNothing()).thenThrow(new NullPointerException());
    // doXXから始める(戻り値あり)
    doReturn("mogemoge").when(mogeService).returnStr();
    // doXXから始める(戻り値なし)
    doThrow(new NullPointerException()).when(mogeService).returnNothing();
```

条件→結果の順に読めるので個人的には`when()`から始める記法が好みですが、
そちらを使うと戻り値の有無で記述が変わるため、まあ一長一短ではあります。

そしてもう一点、mock側メソッドへの引数の扱いですが、
上記例では記載ないものの、仮に以下のような引数を取るメソッドをmock化するとします。

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

  public String returnArg(String arg1, String arg2) {
    return arg1 + arg2;
  }
}
```

その場合は「どういう引数を受け取った時の挙動なのか」を指定しなければなりません。
以下の例では`any()`を指定していますが、
これは「どんな引数が来ても、`returnArg()`が呼び出されたら」という指定になります。
```java
    when(mogeService.returnArg(any(), any())).thenReturn("mogemoge");
```

「特定の値が`returnArg()`に渡されたら」という指定は以下です。
```java
    when(mogeService.returnArg("hoge", "moge")).thenReturn("bar");
    when(mogeService.returnArg(eq("foo"), eq("bar"))).thenReturn("bar");
```

`any()`や`eq()`はMatcherと呼ばれるものです。
上記のように、値をそのまま入れても、`eq()`で囲んでもどちらでも動きますが
Matcherを使いたい時はMatcherで統一する必要があるため、
混ぜて使うとMockitoに怒られて動きません。

```java
    // これはダメ
    when(mogeService.returnArg("hoge", any())).thenReturn("bar");
    when(mogeService.returnArg("hoge", eq("moge"))).thenReturn("bar");

    // こう書く
    when(mogeService.returnArg(eq("hoge"), any())).thenReturn("bar");
    when(mogeService.returnArg(eq("hoge"), eq("moge"))).thenReturn("bar");
```

Matcherの有無で記述を変えるのも面倒なので、
常に`eq()`で囲むくらいの気持ちでいいかなと思っています。