Spring BootでモックしたいBeanがテスト対象Beanで宣言されてない場合のMock化をする

# やりたいこと
呼び出しが、以下のような感じ
`Controller -> Service -> Repository -> Component`
`Controller`からとか`Service`からテスト書く時に`@Mock`と`@InjectMocks`では`Component`のBeanをモック化できなかったので`@MockBean`を使用することに

# 環境
- 諸事情あり、JUnit4を使ってます

```build.gradle
plugins {
	id 'org.springframework.boot' version '2.2.5.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
}

group = 'com.example.mock'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
}

test {
	useJUnitPlatform()
}
```

# 🍥MockBean

### ソース

```SampleContoller.java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/")
public class SampleController {

    SampleService sampleService;

    public SampleController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    public String controllerから文字列返す() {
        return sampleService.serviceから文字列返す();
    }
}
```
```SampleService.java
import org.springframework.stereotype.Service;

@Service
public class SampleService {

    SampleRepository sampleRepository;

    public SampleService(SampleRepository sampleRepository) {
        this.sampleRepository = sampleRepository;
    }

    String serviceから文字列返す() {
        return sampleRepository.repositoryから文字列返す();
    }
}
```
```SampleRepository.java
import org.springframework.stereotype.Repository;

@Repository
public class SampleRepository {

    SampleApi sampleApi;

    public SampleRepository(SampleApi sampleApi) {
        this.sampleApi = sampleApi;
    }

    String repositoryから文字列返す() {
        return sampleApi.sampleApiから文字列返す();
    }
}
```
```SampleApi.java
import org.springframework.stereotype.Component;

/**
 * 外部サービスへ呼び出しのクラス
 */
@Component
public class SampleApi {

    public String sampleApiから文字列返す() {
        return "駆け出しエンジニア";
    }
}
```
```SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
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.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {

    @MockBean
    SampleApi sampleApi;

    @Autowired
    SampleController sut;

    @Test
    public void mockBean使ってテスト() {
        when(sampleApi.sampleApiから文字列返す()).thenReturn("抜け出しエンジニア");
        assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
    }
}
```

### 結果
モックされてる

```
org.opentest4j.AssertionFailedError: 
Expecting:
 <"抜け出しエンジニア">
to be equal to:
 <"駆け出しエンジニア">
but was not.
Expected :駆け出しエンジニア
Actual   :抜け出しエンジニア
```

# 🍥Mockでも試してみる
使うクラスは上と一緒です
`@Mock`つけて、テスト対象クラスに`@InjectMock`

## SampleController -> SampleApi

### ソース

```SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {

    @Mock
    SampleApi sampleApi;

    @InjectMocks
    @Autowired
    SampleController sut;

    @Test
    public void mockBean使ってテスト() {
        initMocks(this);
        when(sampleApi.sampleApiから文字列返す()).thenReturn("抜け出しエンジニア");
        assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
    }
}
```

### 結果

モックされてる気がしない

```.java
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

2020-03-09 22:27:32.270  INFO 3106 --- [           main] c.e.mock.mockdemo.SampleControllerTest   : Starting SampleControllerTest on kihisakawaryoutsuginoMacBook-Pro.local with PID 3106 (started by ri2kku in /Users/xxx/sandbox/java/mock-demo)
2020-03-09 22:27:32.271  INFO 3106 --- [           main] c.e.mock.mockdemo.SampleControllerTest   : No active profile set, falling back to default profiles: default
2020-03-09 22:27:33.164  INFO 3106 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-09 22:27:33.352  INFO 3106 --- [           main] c.e.mock.mockdemo.SampleControllerTest   : Started SampleControllerTest in 1.305 seconds (JVM running for 1.965)
2020-03-09 22:27:33.798  INFO 3106 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0
```

## SampleController -> SampleService

### ソース
```SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {

    @Mock
    SampleService sampleService;

    @InjectMocks
    @Autowired
    SampleController sut;

    @Test
    public void mockBean使ってテスト() {
        initMocks(this);
        when(sampleService.serviceから文字列返す()).thenReturn("抜け出しエンジニア");
        assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
    }
}
```

### 結果
モックされてる

```
org.opentest4j.AssertionFailedError: 
Expecting:
 <"抜け出しエンジニア">
to be equal to:
 <"駆け出しエンジニア">
but was not.
Expected :駆け出しエンジニア
Actual   :抜け出しエンジニア
```


# まとめ
モックしたいBeanがテスト対象Beanで宣言されてない場合は、MockBean使うのがいいのかね。
そもそもモックしたくない。