はじめに
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層をモックしてテストすることとします。
@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);
}
}
@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);
}
}
@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されるクラスでも実現できるようになります。
リンク
- 公式ドキュメント
- Spring Boot 1.4: @MockBean and @SpyBean(Gooroo)
- Difference between @Mock, @MockBean and Mockito.mock() (StackOverflow)