-
테스트백기선(인프런 강의)/스프링 부트 개념과 활용 2020. 4. 16. 23:43반응형
테스트란
테스트 파일에는 @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"); } }
반응형'백기선(인프런 강의) > 스프링 부트 개념과 활용' 카테고리의 다른 글
Spring Web MVC -HttpMessageConverters (0) 2020.04.22 Spring-Boot-Devtools (0) 2020.04.21 스프링 부트 기본 로거 설정 (0) 2020.04.16 프로파일 (0) 2020.04.14 외부 설정 2부 (0) 2020.04.13 - mockMvc.perform(get("/hello"))