プロジェクト内の依存クラスが増加するにつれて、依存クラス全てを読み込む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));
}
}