테스트란?

테스트 파일에는 @SpringBootTest 어노테이션이 붙어있는데, @SpringBootTest 어노테이션은 @SpringBootApplication 어노테이션이 붙어있는 스프링 메인 애플리케이션을 찾아간다. 그리고 메인에서 부터 시작하는 모든 Bean 스캔을 한다. 그 모든 것을 테스트용 애플리케이션 context를 만들면서 모든 Bean을 등록해준다. 그 후 MockBean을 찾아서 그 Bean만 교체해준다. 교체된 MockBean은 테스트마다 리셋된다.

테스트 예시

아래와 같이 SampleController가 SampleService 호출해서 /hello라는 요청dmf 보내면 hello 메서드가 실행되는 코드가 있다고 한다.

스크린샷 2020-02-07 오전 2.20.38.png

이 코드를 테스트 해보려고 한다.

SampleController에서 command+N 키를 눌러 Test 파일을 생성해준다.

스크린샷 2020-02-07 오전 2.23.04.png

test를 하려면 pom.xml에 아래의 의존성이 있어야한다.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

클래스를 public으로 바꿔주고, @SpringBootTest 어노테이션과 @RunWith(SpringRunner.class) 어노테이션을 붙여준다.

스크린샷 2020-02-07 오전 2.49.32.png

@SpringBootTest 어노테이션에는 webEnvironment라는 속성이 있다. 기본 값은 MOCK이다.

스크린샷 2020-02-07 오전 2.50.28.png

Mock 타입 테스트

Mock타입은 서블릿 컨테이너(톰캣 같은 것)를 테스트용으로 띄우지 않고, Mockup을 해서 서블릿을 Mocking 한 것을 띄워준다. dispatcherServlet이 만들어지긴 하는데 Mockup이 되어, dispatcherServlet에 요청을 보내는 것 '처럼' 실험을 할 수 있다. 이 때 mockup 된 서블릿과 상호작용을 하려면 MockMVC라는 클라이언트를 사용해야 한다.

MockMVC 라는 클라이언트를 사용하려면 @AutoConfigureMockMvc 어노테이션을 붙여주고, 주입받아주면 된다.

스크린샷 2020-02-07 오전 2.51.16.png

그 후 테스트를 작성하면 된다. perform(get())은 get요청을 보내는 것이고, andExpect는 ~하길 기대하는 것이다. 마지막으로 andDo는 ~해달라는 뜻이다.(어떤 Controller를 썼냐, 어떤 Controller의 어떤 method를 호출했냐 등의 다른 옵션들도 존재)

스크린샷 2020-02-07 오전 2.52.04.png

(hello라는 get요청을 보내고, result 값이 200으로 오길 바라고, 내용이 hello junseo이길 바라며, 출력해달라는 뜻)

hello를 실행해보면 아래와 같이 뜬다.

스크린샷 2020-02-07 오전 2.54.08.png

RANDOM-PORT / DEFINED-PORT 타입

실제로 서블릿 컨테이너(톰 캣)가 뜬다. 테스트용 rest template이나, 테스트용 웹 클라이언트를 사용해야 한다.

RANDOM-PORT는 랜덤한 포트를 배정받고, DEFINED-PORT는 포트를 정해줄 수 있다.

TestRestTemplate

TestRestTemplate을 주입받는다.

스크린샷 2020-02-07 오전 2.58.51.png

테스트를 작성해준다. url을 주고, 원하는 바디 타입을 준 후, assertThat으로 확인해준다.

스크린샷 2020-02-07 오전 3.03.13.png

만약 테스트를 SampleService까지 가지말고, SampleController 까지만 테스트 하고 싶을 때는 @MockBean 어노테이션을 사용하여 Service를 만들어 컨트롤 할 수 있다. @MockBean으로 SampleService를 받아준다.

스크린샷 2020-02-07 오전 3.11.24.png

그러면 application context안에 들어있는 이 SampleService bean을 @MockBean으로 교체해준다. 그래서 실질적으로 SampleService는 mockSampleService를 사용하게 된다.

이 mockSampleService로 테스트 해보려면 아래와 같이 테스트 코드를 작성할 수 있다.

스크린샷 2020-02-07 오전 3.13.12.png

원래 SampleService에서 getName은 junseo라는 문자열을 리턴하지만, mockSampleService는 js라는 문자열을 리턴한다.

우리는 테스트 할 때 mockSampleService를 사용하므로 테스트를 돌리면 hello junseo가 아닌 hello js가 된다.

스크린샷 2020-02-07 오전 3.15.05.png

즉, SampleController가 사용하는 SampleService를 mocking해서 Bean을 mockSampleService로 교체한 것이다.

WebTestClient

WebClient는 Rest client 중에 하나이다.

기존에 사용하던 Rest client는 synchronous(동기)이다. 즉, 요청 하나 보내고, 끝날 때까지 기다린 후, 요청을 보낼 수 있는데, webClient는 asynchronous(비동기)이다. 요청을 보내고, 기다리지 않고, 응답이 오면 call back이 오는데, 그 때 call back을 실행할 수 있는 것이다.

WebTestClient는 테스트 할 때, 이런 WebClient와 비슷한 API를 사용할 수 있다.

WebClient를 사용하려면 spring web flux 의존성이 들어와있어야한다.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

테스트를 작성해준다. get().uri().exchange()로 get요청을 보내고, status와 body를 체크해준다.

스크린샷 2020-02-07 오전 3.28.04.png

슬라이스 테스트

테스트는 모든 Bean을 등록한다. 그래서 테스트 할 Bean만 등록하고 싶을 때 사용하는 것이 슬라이스 테스트이다.

레이어 별로 잘라서 적용한다.

@WebMvcTest 어노테이션으로 테스트 해 볼 수 있다. @WebMvcTest 어노테이션은 클래스를 특정해서 테스트 할 수 있다.

그리고 웹 레이어(@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver)만 Bean으로 등록해준다는 특징이 있다. @Service, @Repository 같은 Bean들은 Bean으로 등록해주지 않는다.

따라서, 테스트 시 의존성이 깨지기 때문에 위의 방법 처럼 @MockBean으로 받아와서 사용한다.

테스트 시, MockMvc를 사용한다.

스크린샷 2020-02-07 오전 3.48.28.png

이렇게 하면, SampleController 하나만 Bean으로 등록되어, 훨씬 가벼운 테스트가 된다.

@WebMvcTest 말고도, @JsonTest, @WebFluxTest, @DataJpaTest 등도 슬라이스 테스트 방법이다.

OutputCaptureRule

junit의 Rule을 확장해서 만든 것이다.

로그를 비롯한 console에 찍히는 모든 것을 캡처한다.

SampleController에 로거를 만들어 logger를 찍었을 때,

스크린샷 2020-02-07 오후 9.30.36.png

테스트 클래스에서 OutputCaptureRule을 선언해서(꼭 public으로 해야한다.) 테스트 해볼 수 있다.

스크린샷 2020-02-07 오후 9.31.28.png

0개의 댓글