プロジェクト内の依存クラスが増加するにつれて、依存クラス全てを読み込むSpringBootTestは起動が重くなっていきます。
全てのテストクラスをSpringBootTestで実行すると、全体のテスト実行時間がかなり長くなってしまい開発に影響を及ぼします。
少しでもテスト実行時間を減らすために、全てSpringBootTestを使ってテストをするのではなくレイヤごとに必要な依存クラスのみを読み込ませることで時間を短くすることができます。
Controller層(@WebMvcTest)
MockMvc用でのController層に必要な依存クラスを読み込みます。
テスト対象
DemoApi.java
public interface DemoApi { @RequestMapping("/demoApi") ResponseEntity<DemoApiDto> get(DemoApiDto body); }
修正前(@SpringBootTest)
@SpringBootTest public class DemoApiSpringBootTest { private MockMvc mockMvc; @Autowired private DemoApi demoApi; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(demoApi) .setControllerAdvice(new DemoHandler()) .build(); } @Test void isOK() throws Exception { // GIVEN DemoApiDto demoApiDto = DemoApiDto.builder() .demoName("hogeDemo") .demoObjectDto( DemoObjectDto.builder() .demoObjName("pugeObjName") .build()) .build(); ObjectMapper objectMapper = new ObjectMapper(); String request = objectMapper.writeValueAsString(demoApiDto); // WHEN // THEN this.mockMvc .perform(get("/demoApi") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(request)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().isOk()); } }
修正後(@WebMvcTest)
@WebMvcTest(controllers = DemoApiController.class) public class DemoApiWebMvcTest { private MockMvc mockMvc; @Autowired private DemoApi demoApi; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(demoApi) .setControllerAdvice(new DemoHandler()) .build(); } @Test void isOK() throws Exception { // GIVEN DemoApiDto demoApiDto = DemoApiDto.builder() .demoName("hogeDemo") .demoObjectDto( DemoObjectDto.builder() .demoObjName("pugeObjName") .build()) .build(); ObjectMapper objectMapper = new ObjectMapper(); String request = objectMapper.writeValueAsString(demoApiDto); // WHEN // THEN this.mockMvc .perform(get("/demoApi") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(request)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().isOk()); } }
Controllerがさらに依存クラスを持っている場合は、includeFiltersオプションを使います。
例えば、このControllerクラスに「HogeComponent」と「PugeService」が依存していた場合は以下のようにWebMvcTestを使います。
@WebMvcTest(controllers = DemoApiController.class, includeFilters = @ComponentScan.Filter(classes = {HogeComponent.class, PugeService.class}, type = FilterType.ASSIGNABLE_TYPE))
Service、Component層(@SpringJUnitConfig)
必要な依存クラスだけを読み込ませます。
テスト対象
DemoService.java(Componentを依存クラスとして持っている)
@Service @RequiredArgsConstructor class DemoService { private final DemoComponent demoComponent; int triangleAreaTimesHeightByCalculation(int base, int height, int times){ int timesHeight = demoComponent.timesCalculation(height,times); return base * timesHeight; } }
修正前(@SpringBootTest)
@SpringBootTest public class DemoServiceDISpringBootTest { @Autowired private DemoService target; @Test void triangleAreaTimesHeightByCalculationTest(){ //GIVEN int base = 6; int height = 3; int times = 2; int excepted = 36; //WHEN int actual = target.triangleAreaTimesHeightByCalculation(base, height,times); //THEN assertThat(actual).isEqualTo(excepted); } }
修正後(@SpringJUnitConfig)
@SpringJUnitConfig(classes = DemoServiceDISpringJunitConfigTest.Config.class) public class DemoServiceDISpringJunitConfigTest { @Autowired private DemoService target; @ComponentScan({ "com.example.demo.service", "com.example.demo.component" }) static class Config { } @Test void triangleAreaTimesHeightByCalculationTest() { //GIVEN int base = 6; int height = 3; int times = 2; int excepted = 36; //WHEN int actual = target.triangleAreaTimesHeightByCalculation(base, height, times); //THEN assertThat(actual).isEqualTo(excepted); } }
staticクラスを作り、依存するクラスをComponentScan対象にしていします。
そのクラスをアノテーションで指定することで、対象のクラスのみが読み込まれます。
Repository層(@MybatisTest)
Mybatisでデータベースアクセスを行っている場合に使えます。
データベースアクセスに必要な依存クラスを読み込みます。
テスト対象
AddressMapper.java
@Mapper public interface AddressMapper { int create(Address address); @Results(id = "AddressByDistinct", value = { @Result(column = "address_id", property = "addressId"), @Result(column = "address", property = "address"), @Result(column = "address2", property = "address2"), @Result(column = "district", property = "district"), @Result(column = "city_id", property = "city.cityId"), @Result(column = "postal_code", property = "postalCode"), @Result(column = "phone", property = "phone"), @Result(column = "last_update", property = "lastUpdate") }) @Select("SELECT * FROM address WHERE district = #{district}") List<Address> getAddressByDistrict(@Param("district") String distinct); }
修正前(@SpringBootTest)
@SpringBootTest class AddressMapperSpringBootTest { @Autowired private AddressMapper addressMapper; @Test void assertionTest() { //GIVEN String distinct = "Bihar"; //WHEN List<Address> actual = addressMapper.getAddressByDistrict(distinct); //THEN //フィールドの値が全てNullではない assertThat(actual).extracting("address").allMatch(Objects::nonNull); //フィールドの値が全て空文字 assertThat(actual).extracting("address2").allMatch(i -> "".equals(i)); //フィールドの値が全てリストで指定した値 assertThat(actual).extracting("city.cityId").allMatch( i -> List.of(264, 110, 346).contains(i)); } }
修正後(@MybatisTest)
@MybatisTest class AddressMapperMybatisTest { @Autowired private AddressMapper addressMapper; @Test void assertionTest() { //GIVEN String distinct = "Bihar"; //WHEN List<Address> actual = addressMapper.getAddressByDistrict(distinct); //THEN //フィールドの値が全てNullではない assertThat(actual).extracting("address").allMatch(Objects::nonNull); //フィールドの値が全て空文字 assertThat(actual).extracting("address2").allMatch(i -> "".equals(i)); //フィールドの値が全てリストで指定した値 assertThat(actual).extracting("city.cityId").allMatch( i -> List.of(264, 110, 346).contains(i)); } }
あらかじめプロパティファイルなどで設定している接続先のDBをテストで使用する場合は、@AutoConfigureTestDatabaseアノテーションも使用します。
@MybatisTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class AddressMapperMybatisTest { @Autowired private AddressMapper addressMapper; @Test void assertionTest() { //GIVEN String distinct = "Bihar"; //WHEN List<Address> actual = addressMapper.getAddressByDistrict(distinct); //THEN //フィールドの値が全てNullではない assertThat(actual).extracting("address").allMatch(Objects::nonNull); //フィールドの値が全て空文字 assertThat(actual).extracting("address2").allMatch(i -> "".equals(i)); //フィールドの値が全てリストで指定した値 assertThat(actual).extracting("city.cityId").allMatch( i -> List.of(264, 110, 346).contains(i)); } }
コメントを書く