67
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-31

はじめに

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層をモックしてテストすることとします。

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);
	}


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);
	}
}
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されるクラスでも実現できるようになります。

リンク

67
70
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?