Mockitoで作成した様々なモックオブジェクト

# 概要

まずはMockitoの使用のチュートリアルを挟み、
後半では筆者の作成した記事「[様々なテストダブル](https://qiita.com/maple_syrup/items/545b890268315e273227#%E6%A7%98%E3%80%85%E3%81%AA%E3%83%86%E3%82%B9%E3%83%88%E3%83%80%E3%83%96%E3%83%AB)」において紹介した
モックオブジェクトをMockitoを使用して実装してみる。

# Mockitoの使い方

## 事前準備

[プロジェクトサイト](http://code.google.com/p/mockito/)からjarファイルをダウンロードする。
mockito-all-X.X.X.jarをビルドパスに追加する。

## 実装のイロハ

以下のコードを参考に、モックオブジェクトの基本的な操作を理解する。

```java:未実装のクラス(スタブの差し替え対象)
public class UnfinishedLogic {

    int x;
    boolean flag;
    String str;
    BigDecimal decimal;

	public String doSomething() {
        return "未実装";
    }
}
```

```java:テストコード
@Test
public void モックオブジェクトのイロハ() {
    // 1. モックオブジェクトの作成
    UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
    // 2. スタブの戻り値
    assertThat(mock.x, is(0));
    assertThat(mock.flag, is(false));
    assertThat(mock.str, is(nullValue()));
    assertThat(mock.decimal, is(nullValue()));
    assertThat(mock.doSomething(), is(nullValue()));
    // 3. スタブメソッドの定義
    when(mock.doSomething()).thenReturn("想定値");
    assertThat(mock.doSomething(), is("想定値"));
}
```

### モックオブジェクトの作成

モックオブジェクトを作成するときは、org.mokito.Mokitoクラスのmockメソッドに
モック化したいクラスの型オブジェクトを指定する。
渡すクラスにfinalクラスを指定すると、実行時に例外が発生するので注意。

なお、Mockitoにおいてモックとスタブに実装の違いはない。
基本的には、目的の違いによってメソッドを利用するだけならスタブオブジェクト、
メソッド呼び出しの検証を行うものをモックオブジェクトと呼ぶが、人によってまちまち。


```java
    UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
```

### スタブの戻り値

スタブオブジェクトの戻り値は、モックの定義(後述)前だと
メンバ、メソッド共に、null(プリミティブ型の場合はデフォルト値)を返却する。

```java
    // プリミティブ型
    assertThat(mock.x, is(0));
    assertThat(mock.flag, is(false));
    // オブジェクト
    assertThat(mock.str, is(nullValue()));
    assertThat(mock.decimal, is(nullValue()));
    // スタブメソッド（定義前）
    assertThat(mock.doSomething(), is(nullValue()));
```

### スタブメソッドの定義

モックオブジェクトに定義されたスタブメソッドは、
「こういう引数が渡されるとこういう戻り値が返る」といった形で
戻り値を再定義することができる。

詳細は後述していくが、基本的な流れとしては
org.mockito.Mockitoクラスのwhenメソッドでorg.mockito.stubbing.OngoingStubbingクラスを
返却し、OngoingStubbingクラスに定義されている様々なstaticメソッドを利用する。

```java
    // 「doSomething()を呼び出す。すると、"想定値"が返却される。」
    // といった風に自然言語的にメソッドを定義できる
    when(mock.doSomething()).thenReturn("想定値");
    // 再定義されたことで、doSomething()の戻り値が"想定値"になっている
    assertThat(mock.doSomething(), is("想定値"));
```

# その他のスタブメソッド

## 例外を送出するスタブメソッド

### 〜戻り値があるメソッドの場合〜

```java:プロダクションコード
    public void connectDBWithInfo(String user) throws SQLException {
        // DBに接続する
        return "DBの接続情報";
    }
```

```java:テストコード
    @Test
    public void 例外が発生するスタブ() {
        UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
        try {
            when(mock.connectDB("unExistUser")).thenThrow(new SQLException());
            mock.connectDBWithInfo("unExistUser");
        } catch (SQLException e) {
            System.out.println("SQLExceptionの発生");
        }
    }
```

```text:テスト実行時のコンソール出力
SQLExceptionの発生
```

### 〜戻り値がないメソッドの場合〜

```java:プロダクションコード
    public void connectDB(String user) throws SQLException {
        // DBに接続する
    }
```

```java:テストコード
    @Test
    public void 例外が発生するスタブ2() {
        UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
        try {
            doThrow(new SQLException()).when(mock).connectDB("unExistUser");
            mock.connectDB("unExistUser");
        } catch (SQLException e) {
            System.out.println("SQLExceptionの発生");
        }
    }
```

```text:テスト実行時のコンソール出力
SQLExceptionの発生
```
### 〜任意の引数をとる形でテストしたい場合〜

テストケースで50通りの入力値をテストしたいとなった場合に、
今までの書き方だと50行のwhenメソッドが必要となる。
そういった場合はMockitoのMatchersクラスの提供するanyXXXメソッドを使用する。

```java:プロダクションコード
    public int returnConstNum(int anyNum) {
        return 0;
    }
```

```java:テストコード
    @Test
    public void 任意の値をとるメソッドの検証() {
        UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
        when(mock.returnConstNum(anyInt())).thenReturn(1);
        // どんな数値を渡しても実測値が1になっている
        assertThat(mock.returnConstNum(1), is(1));
        assertThat(mock.returnConstNum(5), is(1));
        assertThat(mock.returnConstNum(9999), is(1));
    }
```

## メソッドの呼び出し状況を検証する

モック的な利用方法。メソッドがどんな引数で、何回呼ばれたかを検証する。
Mockitoクラスのverifyメソッドを利用し、
```verify(対象モックオブジェクト, 呼び出し回数).検証するメソッド```を基本の形として実装する。

```java
    @Test
    public void verifyによる検証() {
        List<String> mock = Mockito.mock(List.class);
        mock.clear();
        mock.add("arg1");
        mock.add("arg1");
        Mockito.verify(mock).clear();
        Mockito.verify(mock, times(2)).add("arg1");
        Mockito.verify(mock, never()).add("arg2");
    }
```

### 〜verifyメソッドに渡せる値(VerificationMode)〜

| メソッド | 判定できる呼び出し条件 |
|:--|:--|
| times(n) | n回 |
| atLeastOnce() | 最後に呼び出されたメソッド |
| atLeast(n) | n回以上(n ≦) |
| atMost(n) | n回以上(n ≧) |
| never() | 一度も呼ばれていない |

## オブジェクトの一部をモック化する

Mockitoでは、Mockitoクラスのspyメソッドに実オブジェクトを渡すことで
実オブジェクトの一部をモック化することができる。
spyメソッドで作成されたオブジェクトのメソッドを呼び出すとき、
スタブメソッドを定義されていないメソッドは実オブジェクトが実行される。

```java:スパイオブジェクトのテストコード
    @Test
    public void 実オブジェクトの一部モック化() {
        UnfinishedLogic sut = new UnfinishedLogic();
        UnfinishedLogic spy = spy(sut);
        when(spy.doSomething()).thenReturn("mockString");
	// 再定義しているので定義した戻り値が返却される
        assertThat(spy.doSomething(), is("mockString"));
        // 再定義していないので実オブジェクトの戻り値 0 が返却される
        assertThat(spy.returnConstNum(1), is(0));
    }
```
## スパイオブジェクト

プロダクションコードは[筆者の記事のOutputLogクラス](https://qiita.com/maple_syrup/items/545b890268315e273227#%E3%82%B9%E3%83%91%E3%82%A4-%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%82%92%E7%9B%A3%E8%A6%96%E3%81%99%E3%82%8B%E3%83%A2%E3%83%83%E3%82%AF)を参照。

```java:テストコード
    @Test
    public void スパイオブジェクトの作成() {
        OutputLog sut = new OutputLog();
        Logger spy = spy(sut.logger);
        StringBuffer infoLog = new StringBuffer();
        // org.mockito.stubbing.Stubberクラスを返却
        doAnswer(new Answer<Void>() {
            // 戻り値のないメソッドにコールバック関数を適用する
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                // スパイオブジェクトで独自に追加する処理
                infoLog.append(invocation.getArguments()[0]);
                // 実際のメソッドの呼び出し
                invocation.callRealMethod();
                return null;
            }
        }).when(spy).info(anyString());
        // テスト対象クラスのロガーをスパイオブジェクトに差し替える
        sut.logger = spy;
        sut.outputLog();
        assertThat(infoLog.toString(), is("doSomething"));
    }
```

### doAnswerメソッド

スタブに対して、どのようにメソッドを実行するかを定義する。
引数にorg.mockito.stubbing.Answerインターフェースをとることで、
Answerインターフェースの実装クラスで定義されたメソッドを実行する仕組みを提供する。
具体的には、org.mockito.stubbing.Stubberクラスを返却する。

### Answerインターフェース

下記のメソッドのみが定義されたインターフェース。よって、ラムダによる実装も可能。
```T answer(InvocationOnMock invocation) throws Throwable```
answerメソッドではスタブメソッドにおいて行う処理を柔軟に変更することができる。

### InvocationOnMockクラス

実オブジェクトに関して様々な操作を行うためのクラス。


| メソッド定義 | 処理内容 |
|:----|:----|
| Object callRealMethod() | 実オブジェクトのメソッドを呼び出す |
| <T> T getArgumentAt(int index, Class<T> clazz) | メソッドの引数の位置を指定して取得する |
| Object[] getArguments() | メソッドの引数を全て取得する |
| Method getMethod() | 実オブジェクトのメソッドのメタ情報を取得する |
| Object getMock() | モックオブジェクトを取得する |

# 参考文献

この記事は以下の情報を参考にして執筆しました。

- [JUnit実践入門 ── 体系的に学ぶユニットテストの技法](https://www.amazon.co.jp/JUnit%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-%E4%BD%93%E7%B3%BB%E7%9A%84%E3%81%AB%E5%AD%A6%E3%81%B6%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E6%8A%80%E6%B3%95-WEB-PRESS-plus-ebook/dp/B07JL78S95)