티스토리 뷰

백기선 - 스프링 부트 개념과 활용

6-1. 테스트

  • 시작은 일단 spring-boot-starter-test를 추가하는 것 부터

    • test scope으로 추가

@SpringBootTest

  • @SpringBootTest가 하는 역할은 @SpringBootApplication을 찾아서 테스트를 위한 빈들을 다 생성한다. 그리고 @MockBean으로 정의된 빈을 찾아서 교체한다.

  • @RunWith(SpringRunner.class)랑 같이 써야 함

  • 빈 설정 파일은 안해주나? 알아서 찾는다. (@SpringBootApplication)

SpringBootTest.webEnvironment

  • MOCK : mock servlet environment. 내장 톰캣 구동 안함.

    package io.namjune.springbootconceptandutilization.sample;

    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;

    @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(content().string("hello namjune"))
              .andDo(print());
      }
    }
  • RANDOM_PORT, DEFINED_PORT: 내장 톰캣 사용 함

    package io.namjune.springbootconceptandutilization.sample;

    import static org.assertj.core.api.Assertions.assertThat;

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.test.context.junit4.SpringRunner;

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    public class SampleControllerTest {

       @Autowired
       TestRestTemplate testRestTemplate;

       @Test
       public void hello() {
           String result = testRestTemplate.getForObject("/hello", String.class);
           assertThat(result).isEqualTo("hello namjune");
      }
    }
  • spring5 webflux에서 추가된 RestClient중에 하나인 WebTestClient도 사용할 수 있다. 기존에 사용하던 WebClient는 synchronous하게 동작하기 때문에 요청 하나 보내고 그 요청이 끝나고 난 다음에 다음 요청을 보낼 수 있었지만, WebTestClient는 asynchronous하게 동작하므로 요청을 보내고 기다리지 않는다. 후에 응답이 오면, 콜백 이벤트를 실행할 수 있다. 따라서, Test코드에서도 WebClient와 비슷한 API를 사용할 수 있다.

    implementation('org.springframework.boot:spring-boot-starter-webflux')
    ...
    package io.namjune.springbootconceptandutilization.sample;

    import static org.mockito.Mockito.when;

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.reactive.server.WebTestClient;

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    public class SampleControllerTest {

       @Autowired
       WebTestClient webTestClient;

       @MockBean
       SampleService mockSampleService;

       @Test
       public void hello() {
           when(mockSampleService.getName()).thenReturn("kim");

           webTestClient.get().uri("/hello").exchange()
              .expectStatus().isOk()
              .expectBody(String.class).isEqualTo("hello kim");

      }
    }
  • NONE: 서블릿 환경 제공 안 함.

@MockBean

  • 위의 경우 테스트가 너무 크다. Controller 테스트코드에서 Service단까지 흘러간다. 컨트롤러만 테스트하고 싶을 경우 서비스 객체를 MockBean으로 만들어서 사용할 수 있다.

  • ApplicationContext에 들어있는 빈을 Mock으로 만든 객체로 교체함

  • 모든 @Test 마다 자동으로 리셋. 직접 리셋을 관리 하지 않아도 된다.

package io.namjune.springbootconceptandutilization.sample;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

   @Autowired
   TestRestTemplate testRestTemplate;

   @MockBean
   SampleService mockSampleService;

   @Test
   public void hello() {
       when(mockSampleService.getName()).thenReturn("kim");

       String result = testRestTemplate.getForObject("/hello", String.class);
       assertThat(result).isEqualTo("hello kim");
  }
}

슬라이스 테스트

  • 레이어 별로 잘라서 테스트 하고 싶을 때

  • @JsonTest

    • json 테스트를 하고싶을 경우 @SpringBootTest 대신 @JsonTest를 선언하고, JacksonTester를 주입받아서 사용하면 된다.

  • @WebMvcTest

  • @WebFluxTest

  • @DataJpaTest

  • ...

6-2. 테스트 유틸

  • 스프링 테스트가 제공하는 테스트 유틸리티가 4가지 있다.

    • OutputCapture

    • TestPropertyValues

    • TestRestTemplate

    • ConfigFileApplicationContextInitializer

  • Junit에 있는 Rule을 확장해서 만든 OutputCapture가 제일 많이 쓰인다.

    • OutputCapture는 로그를 비롯해서 콘솔에 찍히는 모든 것을 캡쳐한다.

      • 로그 메세지가 어떻게 찍히는지 테스트할 수 있다.

    • @Rule을 선언하고,

    • Junit이 제공하는 OutputCapture를 public으로 만든다.(@Rule의 제약사항. 빈을 주입받는게 아님)

@RestController
@RequiredArgsConstructor
public class SampleController {

   Logger logger = LoggerFactory.getLogger(SampleController.class);

   private final SampleService sampleService;

   @GetMapping("/hello")
   public String hello() {
       logger.info("hello logger");
       System.out.println("hello sout");
       return "hello " + sampleService.getName();
  }
}

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {

   @Rule
   public OutputCapture outputCapture = new OutputCapture();

   @MockBean
   SampleService mockSampleService;

   @Autowired
   MockMvc mockMvc;

   @Test
   public void hello() throws Exception {
       when(mockSampleService.getName()).thenReturn("kim");

       mockMvc.perform(get("/hello"))
          .andExpect(content().string("hello kim"));

       assertThat(outputCapture.toString())
          .contains("hello")
          .contains("sout");
  }
}







댓글