【JUnit5】MockitoおよびMockBean,SpyBeanの使い方

# 前提：テスト対象のクラス

テストを行いたい対象のクラスは下記であるものとする。
（なお、パッケージの記述は省略）

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@Autowired
SpringService1 springService1;

@RestController
public class SpringRestController {

    @PostMapping(value = "/bbb")
    public ResponseEntity<Void> bbb() throws Exception {
        List<Articles> resultList = new ArrayList<Articles>();

        try{
            resultList = springService1.getArticles("あ", true);
            resultList = springService1.getArticles2("あ", true);
        }catch(Exception e){

        }
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
```

今回は、下記のメソッドをモック化したいものとする。
springService1.getArticles("あ", true);
springService1.getArticles2("あ", true);

# mockitoのアノテーションである@Mockを使ったテストコードの例

それではspringService1.getArticles()とspringService1.getArticles2()を最も初歩的な形でモック化してみる。

**■ポイント**
**・モック化したいフィールドに@Mockをつける。**

**・テスト対象のインスタンスに@InjectMocksをつけることで、対象インスタンスのフィールドに@Mockされたインスタンスを差し込むことができる。
@InjectMocksをつけないと、Mockが動かず、verify()でエラーになる。**

```java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.times;import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;

import com.example.springTest.controller.SpringRestController;
import com.example.springTest.service.SpringService1;

@SpringBootTest
public class SpringRestControllerTest {

    // テスト対象クラス
    @InjectMocks
    SpringRestController springRestController;

    // mockitoのアノテーション
    @Mock
    SpringService1 springTestService;

    // テストケースにこのアノテーションを付与
    @Test
    @DisplayName("bbbのテスト")    
    void bbbのテスト(){
        try {
            // 実行
            var result = springRestController.bbb();

            // 戻り値チェック
            assertEquals(HttpStatus.OK, result.getStatusCode());

            // モックが機能したかを検証
            verify(springTestService,times(1)).getArticles("あ", true);
            verify(springTestService,times(1)).getArticles2("あ", true);

            System.out.println("bbbのテスト実行した");
        } catch (Exception e) {
            e.printStackTrace();            
            fail();
        }
    }
}
```

長ったらしいが、@Mockと@InjectMocksがポイントのところである。
なお、モック化できるのはメンバ変数であり、ローカル変数はモック化できない。重要なことなのでもう一度言います。**テスト対象クラス内で定義しているローカル変数はモック化できない。**
例えば下記のmapはモック化できない。

```java
    public void serviceMethod(){
        Map<String, String> map = new HashMap<String, String>();
        map.put("aaa.12", "111");
        if (map.containsKey("aaa.12")){
            System.out.println("aaa.12が含まれている");
        }
    }
```
when(mapMock.containsKey("aaa.12")).thenReturn(false);としても
verify(mapMock, times(1)).containsKey("aaa.12");
でコケる。


# Springのアノテーションである@MockBeanを使ったテストコードの例

上述した@Mockは、Mockitoのアノテーションである。

**@Mockを使う場合、テスト対象のインスタンス（この場合はspringRestController）には@Autowiredをつけてはならない。@Autowiredをつけると、SpringRestControllerクラス内で@AutowiredされているSpringService1の方が適用され、モックが機能しない。**

**テスト対象のインスタンスに@Autowiredをつけたままモック化したい場合は、@MockBeanアノテーションを使用する。これはSpringが提供するアノテーションであり、@Mockで作ったモジュールと異なり、アプリケーションコンテキスト（DI）に注入されるのが特徴である。**

下記が@MockBeanを使ったテストケースの例である。

長ったらしいが、@InjectMocksを@Autowiredに、@Mockを@MockBeanにそれぞれ変更しただけの違いである。

```java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;

import com.example.springTest.controller.SpringRestController;
import com.example.springTest.service.SpringService1;

@SpringBootTest
public class SpringRestControllerTest {

    // テスト対象クラス
    @Autowired
    SpringRestController springRestController;

    // Springのアノテーション
    @MockBean
    SpringService1 springTestService;

    // テストケースにこのアノテーションを付与
    @Test
    @DisplayName("bbbのテスト")    
    void bbbのテスト(){
        try {
            // モック作成（前置記法）
            // List<Articles> returnList = new ArrayList<Articles>();
            // returnList.add(new Articles());
            // doReturn(returnList).when(springTestService).getArticles("あ", true);
            
            // 実行
            var result = springRestController.bbb();

            // 戻り値チェック
            assertEquals(HttpStatus.OK, result.getStatusCode());

            // モックが機能したかを検証
            verify(springTestService,times(1)).getArticles("あ", true);
            verify(springTestService,times(1)).getArticles2("あ", true);

            System.out.println("bbbのテスト実行した");
        } catch (Exception e) {
            e.printStackTrace();            
            fail();
        }
    }
}
```
@Mockと@MockBean、どちらを使うべきかはケースバイケースかもしれないが、後者の方がDIの対象になるため動作は遅くなるので、まずは@Mockを使う方がいいと思われる（異論があればコメントお願いします）。

# 指定の戻り値を返すようにしたい（前置記法）

上記ではspringTestService.getArticles()とspringTestService.getArticles2()を「do Nothing」みたく、何もしないメソッド化するモックの仕方であった。

しかし、モック化に際しては、何かの戻り値を返すように仕掛けたい場合が多い。

以下は、指定の戻り値を返すモックの例である。
とりあえず、2要素だけ持つリストを返却するように仕掛けてみる。

長ったらしいが、
**doReturn(returnList).when(springTestService).getArticles("あ", true);**
のところがポイントである。

```java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;

import com.example.springTest.controller.SpringRestController;
import com.example.springTest.model.Articles;
import com.example.springTest.service.SpringService1;

@SpringBootTest
public class SpringRestControllerTest {

    // テスト対象クラス
    @InjectMocks
    SpringRestController springRestController;

    // mockitoのアノテーション
    @Mock
    SpringService1 springTestService;

    // テストケースにこのアノテーションを付与
    @Test
    @DisplayName("bbbのテスト")    
    void bbbのテスト(){
        try {
            // モック作成（前置記法）
            List<Articles> returnList = new ArrayList<Articles>();
            returnList.add(new Articles(){{
                setArticleTitle("JUnitテスト");
            }});
            returnList.add(new Articles(){{
                setArticleTitle("JUnitテスト2");
            }});

            doReturn(returnList).when(springTestService).getArticles("あ", true);
            
            // 実行
            var result = springRestController.bbb();

            // 戻り値チェック
            assertEquals(HttpStatus.OK, result.getStatusCode());

            // モックが機能したかを検証
            verify(springTestService,times(1)).getArticles("あ", true);
            verify(springTestService,times(1)).getArticles2("あ", true);

            System.out.println("bbbのテスト実行した");
        } catch (Exception e) {
            e.printStackTrace();            
            fail();
        }
    }
}

```
# ArgumentMatchersを使って、汎用性の高いモックをつくってみる

上述した
```java
doReturn(returnList).when(springTestService).getArticles("あ", true);
```
であるが、モック化したいメソッド（この場合はgetArticles）に記述する引数は、プロダクトコード（テスト対象のコード）で実際に入ることになる値と一字一句違わないものにしないと、モックが働かず、verify()でエラーになる。

テストコードを大量に書く上で少々不便とも言えるので、汎用性の高いモックをつくってみたい。この時有用なのがmockitoのArgumentMatchersだ。

```java
doReturn(returnList).when(springTestService).getArticles("あ", true);
```
をこのように変えてみる。import文は追加する。

```java
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;

doReturn(returnList).when(springTestService).getArticles(anyString(), eq(true));
```

第一引数であるが、String型でありさえすえば何でもOK、という意味になる。
気をつけたいのは第二引数のところで、

```java
doReturn(returnList).when(springTestService).getArticles(anyString(), true);
```
とすると、テスト実行時にorg.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers!というエラーになる。

ひとつでもArgumentMatchersを使ったならば、全ての引数をArgumentMatchers形式に合わせる必要がある点に注意。

なお、このArgumentMatchersはモックをつくる場合だけでなくモック検証（verify）でも使用可能。

```java
// モックが機能したかを検証
verify(springTestService,times(1)).getArticles(anyString(), eq(true));
```

# @Spy,@SpyBean

@Mockは、インスタンスの非staticかつpublicのメソッドすべてをMock化する。
そのため一部のメソッドを実装のまま使いたい場合には適さない。

インスタンスの指定のメソッドのみをモック化し、他は実装の通りに動かしたい場合は、@Spyを使う。
これについて詳しくは別記事を参照。

https://www.shookuro.com/entry/mockito-spy

@MockのDIあり版として@MockBeanがあるように、@SpyBeanというSpringのアノテーションもある。

@Spy、@SpyBeanの典型的な使いどころとしては、テスト対象のメソッドと、モック化したいメソッドが同じクラスにある場合である。下の例では、method2のみモック化し、method1は実装のまま動くことになる。

例：
```java:テスト対象クラス
import org.springframework.stereotype.Service;

@Service
public class SampleService {
    
    public String method1(){
        System.out.println("method1実行");
        String result = method2();
        System.out.println(result);
        return result;
    }

    public String method2(){
        return "モックではないよ";
    }
}
```

```java:テストコード
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;

@SpringBootTest
public class SampleServiceTest {
    
    @SpyBean
    SampleService sampleService;

    @Test
    @DisplayName("method2をモック化してmethod1のテスト")
    public void method1のテスト(){
        doReturn("モックだよ").when(sampleService).method2();
        String result = sampleService0224.method1();
        assertEquals("モックだよ", result);
    }
}
```

```:実行結果
method1実行
モックだよ
```