[補足] org.mockito.exceptions.misusing.UnnecessaryStubbingExceptionについて

### 背景
以下記事は [[Web/まとめ] JUnit5 で Mockitoを利用する方法](https://qiita.com/northernbird/items/90ca7713aa1be7d8ae54)で理解に若干時間のかかった `org.mockito.exceptions.misusing.UnnecessaryStubbingException` について、実際のサンプルコードを踏まえて、理解の足しにした追加メモである。

###一体どのような時に起こるのか?

#### (復習&まとめ)まずMockitoで何ができるのか?

- Mockitoを利用することで、テスト対象コードが利用する依存サービス (3rd party libraryだったり、実際の実装がわからない interface classだったり)への依存なしでテストを行うことができる。
- 例えば、実際の実装を使わずとも、Mockオブジェクトを定義し、そのMock オブジェクトがどのような結果を返すかなどを、開発者は各テストケースに応じて挙動を書き換える (=定義する)ことができる
    - 利用している3rd library interfaceがExceptionをthrowした場合は、自分のテストクラスはどのような挙動をするべきか?
    - 3rd party library interface / method の実装や中身は興味がない、とにかくintegrationが正常に行われていると仮定して、3rd party library interface の挙動をフェイク実装したい


### (復習&まとめ) TestにおけるStubとMockとは?

自分は、Mockitoにおいて、ここやや混同したのでまとめておく。

#### MockとStubの違い

<font color="red">
**Mockitoの場合は StubもMockも作り方は基本的に一緒 (<=ここが自分にとってかなりややこしかった :disappointed:)。Mockの場合は `verify`をコールして動作の検証を行う一方、Stubの場合はそれを行わない(実装が壊れないようにフェイクデータを返すことが責務だから)。**
</font>

##### 参考LINK
- https://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks
- https://pazoo.hatenablog.com/entry/stub_mock_Java (わかりやすい説明 :tada: )
- https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#verification (Mockito公式Doc/Mockについて)
- https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#stubbing (Mockito公式Doc/Stubについて)


##### まとめ

- Mock
    - テスト対象のクラスに対するテストの一環として、(実装は持たない架空の)interface/componentの **予測される定義**を記述し、**実際に期待通りコールされたかをチェックする**
    - **従って、Mockitoの場合は `verify` を呼ぶことが必須!!!**
    - ただ、それ以外の作成方法は一緒！ (例： `@Mock`を利用)

- Stub
    - テストコードがテストケースの意図と異なる箇所で関連のないエラーが発生しないよう、問題ないFakeデータを返すように定義するためだけに、挙動を定義する
    - **従って、Mockitoの場合は `verify` はむしろ実行してはいけない! (Mockito java docではコードが冗長になるのでやめろと言っている**
    - (参考) https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#stubbing
    - ただ、それ以外の作成方法は一緒！ (例： `@Mock`を利用)
    
### (話題の核心) UnnecessaryStubbingExceptionはどのような問題で発生するのか?

ここまで前提知識が詰まったところでようやく核心に入れる。。

このExceptionは、`Mock`についてではなく `Stub`について述べている。
上記で述べたとおり、テストコードを無事テスト意図通りに動かすためだけのFake Dataを返せば良いというタイプのオブジェクトであるが、注意しなければならないのが、、
<font color="red">
**やみくもに Stub Codeをテスト対象コード、テスト意図をきちんと理解しないまま、テストを動かすだけに適当につけていってしまうと、テストコードが読みにくくなり、テストが汚くなってしまうということなのだ!**
</font>

この問題に対してアラートを検知してくれる仕組みがこのExceprtionなのだ！

実際には、Mocktito JavaDocでわかりやすい例を表示してくれていた (https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html)

例：

以下テストでは `before()` メソッドにより、毎テストケース実行時に `when(foo.foo()).thenReturn("ok");` が定義されている。

これは、test1 & test2 では問題なく動作するが、 test3では `foo` に対する interactionが発生していないため、`when(foo.foo()).thenReturn("ok");`というスタブ定義が冗長になってしまい、 `UnnecessaryStubbingException`が発生してしまう。

```java
 public class SomeTest {

     @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(STRICT_STUBS);

     @Mock Foo foo;
     @Mock Bar bar;

     @Before public void before() {
         when(foo.foo()).thenReturn("ok");

         // it is better to configure the stubbing to be lenient:
         // lenient().when(foo.foo()).thenReturn("ok");

         // or the entire mock to be lenient:
         // foo = mock(Foo.class, withSettings().lenient());
     }

     @Test public void test1() {
         foo.foo();
     }

     @Test public void test2() {
         foo.foo();
     }

     @Test public void test3() {
         bar.bar();
     }
 }
```

#### `lenient` 利用のススメ

このtest errorは mock objectに対して `lenient` モードを利用することによって消去することができる。
上記 公式Mockito documentationでは、別にそうして `lenient` モードを利用することは必ずしも悪いことではないと言っている (エラーはデフォルトで出しておきながら、、 :sweat_drops: )

理由としては、、

- 上記のように、`@Before`メソッド時に、繰り返し的にStubの共通挙動を定義することで、コードの冗長化が減る
    - なので、上記例 (test3) のように、一部やむを得ない例外事項が発生してしまったとしても、重複コードの冗長化 (=>`@Before`メソッドの代わりに、各テストメソッドに同じ処理を重複して書き込む)よりは、 **`lenient` モードを使い、(stubの冗長化をトレードオフし)`@Before`メソッドで初期化処理をまとめた方が綺麗である**

- ただ、いずれにしてもケースバイケースなので、開発者自身の best judgement に委ねる


### 私の会社のプロジェクトの場合は、、、 :thinking: 

まさに `lenient` 利用のススメ と同じようなトレードオフ現象に遭遇したわけだが、私のテストの場合は、とりあえず`@Before`メソッドから共通処理を外し、各テストメソッドごとに特有の初期化処理を実装するようにした。

どうしてそのような判断をしたかというと、、、

- テストケースごとに、このオブジェクトはStub初期化処理して、これはしない、というケースが複雑だったから
    - 場合によっては、テストケース時に、`Before` メソッドで定義したStubを resetする必要があった
    - **ちなみに、`reset`を使うこともmockitoのベストプラクティスとしてはお勧めされていない**
        - https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#17
    - このままの状態で `letient`モードを使い共通化を優先してしまうと、後ほど別の開発者(いや私ですら)がテストコードを修正したり、デバッグしたり、となった時、非常に煩雑で読みにくい実装になるだろうと懸念されるから

- また、各Stub初期化処理をメソッドにまとめる (例： `setUpXXXServiceStub` みたいにdescriptiveなメソッド名で)と、別に各テストコード内に記載しても、それなりに読みやすくキープできる
    - 同時に、各テストケース/テスト意図ごとに、オブジェクトがどのようにスタブされるべきであるかが、クリアにわかり良い判断だとは思う