Spring BootでAutowiredされるクラスをMockitoでモックする

# はじめに
Spring Bootで`@Controller` `@Service` `@Repository``@Component`といったアノテーションを付与したクラスはBeanとしてSpringのDIコンテナに登録され、利用するクラス側で`@Autowired`アノテーションを当該クラスに付与することで、Springが生成したオブジェクトを利用できます。

ところで、Mockitoを使って先述のアノテーションを付与したクラスをモックしてテストしたい場合、通常の`@Mock`や`@Spy`**ではなく**、Spring Bootが提供する`@MockBean`もしくは`@SpyBean`アノテーションを当該クラスに付与します。
これらのアノテーションを利用することで、Autowiredされるクラスの状態をモックオブジェクトで制御することができるようになり、単体テストや下位層が未完成あるいはテストで呼び出されるべきではない場合などに役立ちます。

※`@MockBean`または`@SpyBean`はSpring Boot 1.4からの機能です。
https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4#mocking-and-spying


# サンプル
Controller層のテストで、Service層をモックしてテストすることとします。

```Java:ExampleController.java
@RestController
@RequestMapping("/example")
public class ExampleController {
	@Autowired
	private ExampleService exampleService;

	@GetMapping(value = "/some_endpoint")
	public ResponseEntity<List<ExampleEntity>> getExampleEntities(
			@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX") @RequestParam(name = "from") Date from,
			@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX") @RequestParam(name = "to") Date to,
			@RequestParam(name = "limit", defaultValue = "1000", required = false) int limit) 
	{
		if (limit < 0) {
			throw new InvalidRequestFieldsException("Negative limit value is not allowed.");
		}

		List<ExampleEntity> retValue = exampleService.getSomeData(from, to, limit);
		return new ResponseEntity<>(retValue, HttpStatus.OK);
	}
｝

```
```Java:ExampleServiceImpl.java

@Service
public class ExampleServiceImpl implements ExampleService {

	@Autowired
	private ExampleRepository exampleRepository;
	
	// 本来のサービスはRepository層から何らかのデータを取ってきて返している
	@Override
	public List<ExampleEntity> getSomeData(Date from, Date to, int limit) {
		return exampleRepository.getSomeDataFromDb(from, to, limit);
	}
}
```
```Java:ExampleControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ExampleControllerTest {
	// モックサーバー（AutoConfigureMockMvcアノテーションでよしなにしてくれる）
	@Autowired
	private MockMvc mockServer;

	// Service層をモックする
	@MockBean
	private ExampleService exampleService;
	
	// モックオブジェクトの挿入対象
	@InjectMocks
	private ExampleController controller;

	// モックへの引数を取得するためのキャプター
	@Captor
	ArgumentCaptor<Date> fromCaptor;

	@Captor
	ArgumentCaptor<Date> toCaptor;

	@Captor
	ArgumentCaptor<Integer> limitCaptor;

	private static final Date testDateFrom = new Date(1234567890000L);
	private static final Date testDateTo = new Date(1234567890000L);

	@Test
	public void exampleControllerTest() throws Exception {
		// モックしたサービスの挙動を設定する
		// ここでは、どんな引数の場合でも空のリストを返している
		doReturn(Collections.emptyList()).when(exampleService)
				.getSomeData(anyObject(), anyObject(), anyInt());

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
		int limit = 12345;
		
		// APIを叩く
		mockServer.perform(
				MockMvcRequestBuilders.get("/example/some_endpoint")
						.param("from", sdf.format(testDateFrom))
						.param("to", sdf.format(testDateTo))
						.param("limit", String.valueOf(limit)))
				.andDo(MockMvcResultHandlers.print())
				.andExpect(status().isOk())
				.andReturn();

		// モックしたインスタンス内の関数が呼び出されているか
		verify(exampleService, times(1)).getSomeData(fromCaptor.capture(),
				toCaptor.capture(), limitCaptor.capture());
		
		// ※このAssertThatはAssertJを使用した場合の書き方です
		assertThat(fromCaptor.getValue()).isEqualTo(testDateFrom);
		assertThat(toCaptor.getValue()).isEqualTo(testDateTo);
		assertThat(limitCaptor.getValue()).isEqualTo(limit);
	}
}
```
この例ではService層を`@MockBean`することで、本来Service層がRepository層から受け取ったデータを返すところを常に空のリストを返すようにして、Serviceの呼び出し回数と引数を検証しています（簡略化した例なので、検証する意味はほぼ無いですが…）。

`@MockBean`ではなく`@SpyBean`にすればクラスの一部をモックすることもできますし、基本的には通常のクラスにおいて`@Mock`や`@Spy`を指定した時に出来ることがAutowiredされるクラスでも実現できるようになります。

# リンク
- [公式ドキュメント](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html)
- [Spring Boot 1.4: @MockBean and @SpyBean](https://gooroo.io/GoorooTHINK/Article/16943/Spring-Boot-14--MockBean-and-SpyBean/24301#.WabF-5NJbzI)（Gooroo）
- [Difference between @Mock, @MockBean and Mockito.mock() ](https://stackoverflow.com/questions/44200720/difference-between-mock-mockbean-and-mockito-mock)（StackOverflow）