Java/Spring Boot

[Spring Boot] Test

hh_lin 2022. 3. 23. 01:46

1. spring-boot-starter-test

Test를 위해서는 pom.xml 내에 spring-boot-starter-test 의존성 추가 필수

-> test scope으로 추가

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

 

 

 

 

 

 

 

 

 

2. 가장 기본적인 형태의 Test code

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleControllerTest {
   
}

 

 

 

 

 

 

 

 

3. @SpringBootTest -> 통합 테스트

  • RunWith(SpringRunner.class)랑 같이 써야 함
  • Bean 설정 파일은 알아서 찾음 (@SpringBootApplication)
    @SpringBootTest가 @SpringBootApplication을 찾아가서, 
    @SpringBootApplication부터 시작해서 모든 Bean을 스캔해서
    Test용 ApplicationContext를 만들때, Bean으로 등록함
    (그 다음으로 @MockBean을 찾아서 해당 Bean만 Mock으로 만든 객체로 교체)
  • webEnvironment
    - MOCK (mock servlet environment) : 내장 톰캣 구동 안 함
    - RANDOM_PORT, DEFINED_PORT : 내장 톰캣 사용 
    - NONE : 서블릿 환경 제공 안 함

 

 

 

 

 

 

 

 

 

 

4. WebEnvironment.MOCK

: MockMvc Client를 사용해야 함

 

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

   @Autowired
   MockMvc mockMvc;
   
}

-> MockMvc를 만드는 다양한 방법 중 가장 쉬운 방법

 

@Test
public void hello() throws Exception {
   mockMvc.perform(get("/hello"))
      .andExpect(status().isOk())
      .andExpect(content().string("hello hhlin"))
      .andDo(print());
}

 

 

 

 

 

 

 

 

 

 

5. WebEnvironment.RANDOM_PORT

: 내장 톰캣이 뜸. MockMvc가 아니라 TestRestTemplate이나, WebTestClient를 사용해야 함

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

   @Autowired
   TestRestTemplate testRestTemplate;

   @Test
   public void hello() throws Exception {
      String result = testRestTemplate.getForObject("/hello", String.class);
      assertThat(result).isEqualTo("hello hhlin");
   }
}

-> 내장 톰캣 서버에 요청을 보내고 응답을 받아서 확인을 한 것

DEFINED_PORT 사용 시 정의한 포트를 사용 가능하지만 RANDOM_PORT 추천

 

 

 

 

 

 

 

 

 

 

6. @MockBean

  • ApplicatonContext에 들어있는 Bean을 Mock으로 만든 객체로 교체함
  • 모든 @Test마다 리셋되기 때문에 관리 필요없음
  • 서비스단까지 가지 않고, 컨트롤러만 테스트하고 싶은 경우
    @MockBean을 이용해서 끊을 수 있음
  • @SpringBootTest (통합 테스트)와 슬라이스 테스트 모두에서 사용 가능
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

   @Autowired
   TestRestTemplate testRestTemplate;

   @MockBean
   SampleService mockSampleService;

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

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

 

 

 

 

 

 

 

 

 

7. WebTestClient

  • Spring5에서 SpringMVC WebFlux 쪽에 추가된 Rest Client 중 하나
  • WebTestClient는 Asynchronous(비동기)
    요청 하나를 보내고 기다리는 것이 아니라, 응답이 오면 콜백을 실행할 수 있음
    기존의 Rest Client는 Synchronous(동기) : 요청 하나 보내고 끝날 때까지 기다렸다가 다음 요청 보낼 수 있음
  • 의존성 추가해줘야 함 (추가 안 할 경우 NoSuchBeanDefinitionException 발생)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

 

 

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

   @Autowired
   WebTestClient webTestClient;

   @MockBean
   SampleService mockSampleService;

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

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

 

 

 

 

 

 

 

 

8. 슬라이스 테스트

: 수많은 Bean들이 등록되는 것이 싫고, Test할 Bean만 등록하고 싶은 경우

@JsonTest, @WebMvcTest, @WebFluxTest, @DataJpaTest을 이용하면 레이어 별로 잘라서 적용이 됨

 

  • @JsonTest
  • @WebMvcTest
    - 컨트롤러 하나만 테스트
    - 일반적인 @Component들은 Bean으로 등록되지 않고 Web과 관련된
      @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter,
      WebMvcConfigurer, HandlerMethodArgumentResolver만 Bean으로 등록함
    @Service, @Repository는 Bean으로 등록되지 않음

 

@WebMvcTest(SampleController.class)

 

 

  • @WebFluxTest
  • @DataJpaTest
    - @Repository들만 등록이 됨

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.spring-mvc-tests

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

 

 

 

 

 

 

 

 

9. Test Util

  • OutputCaptureRule
    - junit에 있는 Rule을 확장해서 만든 것

    - OutputCaputreRule 객체를 public으로 만들어줘야 함
    - log와, Console에 찍히는 값들을 Test Code로 작성할 수 있음
  • TestPropertyValues
  • TestRestTemplate
  • ConfigFileApplicationContextInitializer

 

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

   @Rule
   public OutputCaptureRule outputCaptureRule = new OutputCaptureRule();

   @Autowired
   MockMvc mockMvc;

   @MockBean
   SampleService mockSampleService;

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

      mockMvc.perform(get("/hello"))
         .andExpect(content().string("hello kvp"))
         .andDo(print());

      assertThat(outputCaptureRule.toString())
         .contains("hhlin")
         .contains("skip");
   }
}

 

 

 

 

 

 

# SampleController

@RestController
public class SampleController {

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

	@Autowired
	private SampleService sampleService;

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

 

# SampleService

@Service
public class SampleService {

   public String getName() {
      return "hhlin";
   }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

스프링 부트 개념과 활용 - 인프런 | 강의

스프링 부트의 원리 및 여러 기능을 코딩을 통해 쉽게 이해하고 보다 적극적으로 사용할 수 있는 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com