테스트란
테스트 파일에는 @SpringBootTest가 아래와같이 붙어있다. @SpringBootTest 어노테이션은 @SpringBootApplication 어노테이션이 붙어있는 스프링 메인 애플리케이션을 찾아간다. 이후 메인부터 시작하는 모든 Bean을 찾는다. 다음으로는 테스트용 애플리케이션 context를 만들면서 Bean을 등록해준다. 이 중에 MockBean에 해당되는 Bean을 찾아서 교체를 해준다. 교체된 MockBean은 테스트마다 리셋이된다.
테스트 예시 코드
@RestController
public class SampleController {
@Autowired
private SampleService sampleService;
@GetMapping("hello")
public String hello() {
return "hello " + sampleService.getName();
}
}
@Service
public class SampleService {
public String getName() {
return "JinSeok";
}
}
위와같이 테스트를 하기위해서는 pom.xml에 아래와 같이 의존성이 정의되어야한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Mock 타입 테스트
Mock타입은 서블릿 컨테이너(톰캣 같은 것)를 테스트용으로 띄우지 않고, Mockup을 해서 서블릿을 Mocking 한 것을 띄워준다. dispatcherServlet이 만들어지긴 하는데 Mockup이 되어, dispatcherServlet에 요청을 보내는 것 처럼 실험을 할 수 있다. 이 때 mockup 된 서블릿과 상호작용을 하려면 MockMVC라는 클라이언트를 사용해야 한다.
MockMVC 라는 클라이언트를 사용하려면 @AutoConfigureMockMvc 어노테이션을 붙여주고, 주입 사용하면 된다.
Mock 객체는 내장 톰캣이 구동이 안된다.
코드설명
- mockMvc.perform(get("/hello"))
- get요청을 보내는 것
- andExpect()
- ~하길 기대하는 것
- andDO();
- ~을 실행
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect((ResultMatcher) content().string("hello JinSeok"))
.andDo(print());
}
}
RANDOM-PORT / DEFINED-PORT 타입
실제로 서블릿 컨테이너(was 종류 - 내장 톰캣)이 뜬다. 테스트용 rest template나, 테스트용 웹 클라이언트를 사용해야 된다.
RANDOMRANDOM_PORT는 랜덤한 포트를 배정받고, DEFINED_PORT는 포트를 정해줄 수 있다.
TestRestTemplate
TestRestTemplate을 주입받아서 사용을 한다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@Test
public void hello() throws Exception {
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello Jinseok");
}
}
@MockBean
SampleService까지 말고 SampleController까지만 가게 하고 싶을 때는 @MockBean을 사용하면 된다. 이 어노테이션을 사용하여 Service를 만들어 컨트롤 할 수 있다.
application context안에 들어있는 SampleService Bean을 @MockBean으로 교체해주기 때문에 mockSampleService를 사용하게 된다.
SampleService에서 getName은 Jinseok을 반환하지만 mockSampleService는 js라는 문자열을 리턴한다.
이 테스트에서는 mockSampleService를 사용하기 때문에 junit5 테스트를 하게 된다면 hello JinSeok이 아닌 hello js가 나오게 된다.
SampleController가 사용하는 SampleService를 mocking해서 Bean을 mockSampleService로 교체를 하였다
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@MockBean
SampleService mockSampleService;
@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("js");
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello js");
}
}
WebTestClient
WebClient는 Rest Client중에 하나이다.
기존에 사용하는 Rest Client는 synchronous(동기)이다. 하지만 WebClient는 asynchronous(비동기)로 되어있다. 요청을 보내고, 기다리지 않고, 응답이 오면 call back이 온다. 이때 call back을 실행을 할 수 있다.
WebTestClient는 테스트할 때 WebClient와 비슷한 API를 사용할 수 있다.
WebClient를 사용할라면 아래와같이 의존성이 추가되어야 한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
status와 body를 체크해 준다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
WebTestClient webTestClient;
@MockBean
SampleService mockSampleService;
@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("js");
webTestClient.get().uri("/hello").exchange().expectStatus().isOk()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello JinSeok");
}
}
슬라이스 테스트
@SpringBootTest 이 어노테이션이 @SpringBootApplication을 찾아간다. 여기서부터 시작하는 Bean을 다 찾고, SpringBootTest에 저장을 한다. Bean들을 찾아 Mock Bean과 교체해 준다. 즉 모든 Bean을 교체 및 사용한다.
위와 다르게 테스트할 Bean만 등록하고 싶을 때 사용하는 것이 슬라이스 테스트이다.
웹 레이어(@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver)만 Bean으로 등록해준다는 특징이 있다. @Service, @Repository 같은 Bean들은 Bean으로 등록해주지 않는다.
그렇기 때문에 테스트 시 의존성이 깨지기 때문에 @MockBean으로 받아서 사용한다.
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {
@Autowired
MockMvc mocMvc;
@MockBean
SampleService mockSampleService;
@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("js");
mocMvc.perform(get("/hello"))
.andExpect(content().string("hello js"));
}
}
위와같이 사용하면 SampleController만 Bean으로 등록되어 가벼운 테스트가 된다.
@WebMvcTest 말고도, @JsonTest, @WebFluxTest, @DataJpaTest 등도 슬라이스 테스트 방법이다.
OutputCapture
junit의 Rule을 확장해서 만든 것이다.
로그를 비롯한 console에 찍히는 모든 것을 캡쳐 한다.
@RestController
public class SampleController {
Logger logger = LoggerFactory.getLogger(SampleController.class);
@Autowired
private SampleService sampleService;
@GetMapping("/hello")
public String hello() {
logger.info("여기는 컨트롤러");
System.out.println("skip");
return "hello " + sampleService.getName();
}
}
테스트 클래스에서 OutputCapture Rule을 선언해서(꼭 public으로 해야한다.) 테스트 해볼 수 있다.
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {
@Rule
public OutputCaptureRule outputcapture = new OutputCaptureRule();
@Autowired
MockMvc mocMvc;
@MockBean
SampleService mockSampleService;
@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("js");
mocMvc.perform(get("/hello"))
.andExpect(content().string("hello js"));
assertThat(outputcapture.toString())
.contains("여기는 컨트롤러")
.contains("skip");
}
}