인프런에서 백기선님의 스프링부트 개념과 활용 강의를 듣고, 개인적으로 공부하며 핵심만 정리한 글입니다.
세팅
테스팅에 들어가기 앞서, Controller 와 Service 클래스를 미리 정의해놓자.
// SampleController.java
@RestController
public class SampleController {
@Autowired
private SampleService sampleService;
@GetMapping("/hello")
public String hello() {
return "hello " + sampleService.getName();
}
}
// SampleService.java
@Service
public class SampleService {
public String getName() {
return "heumsi";
}
}
테스트 코드 작성
1) 가장 기본적인 형태
테스팅 코드를 만들기 위한 가장 기본 형태는 다음과 같다.
// SampleControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleControllerTest {
@Test
public hello_test() {
}
}
2) mockMvc
사용
Controller 에 대한 테스트를 위해 요청을 주고받는 작업을 하기 위해서는
- 테스트 환경을
MOCK
으로 만들어주고 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 heumsi"))
.andDo(print());
}
}
3) testRestTemplate
사용
MOCK
테스팅 환경이 아니라, 랜덤 포트를 하나 열어두고 전체 테스팅 환경을 구동하려면,
- 테스트 환경을
RANDOM_PORT
로 만들어주고 TestRestTemplate
을 사용한다.
예를 들면 다음과 같다.
@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 heumsi");
}
}
4) @MockBean
으로 일부 Bean만 Mock
사용하기
위 3번의 문제점은 모든 Bean 을 다 만들어 테스팅 환경이 무거워진다는 것이다.
위 코드는 클래스 이름에서 볼 수 있듯, Controller 에 대한 테스팅만 해보고자 하는 것이다.
그런데 Controller 내부에 Service 를 이용하는 코드가 있기 때문에, Service 로직에 대한 오류도 Controller 테스팅에서 잡힐 수 있다.
이를 분리하여 Controller 테스팅에만 집중하기 위해, 다음과 같이 Service 를 따로 Mock 하여 기존의 Service 를 오버라이딩 할 수 있다.
즉, ApplicationContext 에 이미 우리가 정의한 Bean 을 Mock 으로 만든 객체로 교체한다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@MockBean
SampleService mockSampleService; // Service 를 Mock 하여 오버라이딩
@Test
public void hello() throws Exception{
when(mockSampleService.getName()).thenReturn("heumsi_mock");
// Mock 한 Service 를 다음과 같이 수정할 수 있다.
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello heumsi_mock");
}
}
5) webTestClient
으로 async 하게 테스트하기
webTestClient
으로 클라이언트 단에서의 요청하는 것을 시뮬레이션 해볼 수 있다.
먼저, spring-boot-starter-webflux
를 pom.xml
의 dependency
에 추가하자.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
그리고 다음과 같이 사용 가능하다.
@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("heumsi_mock");
webTestClient.get().uri("/hello").exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello heumsi_mock");
}
}
슬라이싱 테스트
@SpringBootTest
는 테스팅할 때, 실제로 Spring 이 돌아가는 모든 환경과 Bean 을 구축한다. 따라서 일부 기능만 테스팅하고 싶을 때, 굳이 필요없는 리소스까지 모두 띄우는 것은 좀 오버스러울 수 있다.
일부 레이어만 잘라서 테스팅해볼 수 있는데, 이를 슬라이싱 테스트라고 한다.
다음과 같은 슬라이싱 테스트들을 제공한다.
@JsonTest
@WebMvcTest
@WebFluxTest
@DataJpaTest
예를 들어, @WebMvcTest
으로 웹과 관련된 빈들만 테스팅에 로딩하여 슬라이싱 테스트를 할 수 있다.
1) 단일 Controller 만 테스트
하나의 컨트롤러만 테스팅한다.
로딩되는 Bean 에 Service 가 포함되지 않기 때문에 @MockBean
으로 주입받아야 한다.
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class) // SampleController 만 테스트
public class SampleControllerTest {
@MockBean
SampleService mockSampleService;
// 기본적으로 Service Bean 은 로드 되지 않기 때문에, 반드시 주입시켜줘야 함.
@Autowired
MockMvc mockMvc;
// WebMvcTest 는 반드시 MockMvc 로 테스팅 해야함.
@Test
public void hello() throws Exception{
when(mockSampleService.getName()).thenReturn("heumsi_mock");
mockMvc.perform(get("/hello"))
.andExpect(content().string("hello heumsi_mock"));
}
}
테스트 유틸
1) outputCapture
로 로그가 찍혀있는지 확인
예를 들어, SampleController.java
에 다음과 같이 콘솔 출력을 하는 코드를 넣었다고 해보자.
// SampleController.java
@RestController
public class SampleController {
Logger logger = LoggerFactory.getLogger(SampleController.java)
@Autowired
private SampleService sampleService;
@GetMapping("/hello")
public String hello() {
logger.info("heumsi")
System.out.println("skip")
return "hello " + sampleService.getName();
}
}
이제 테스팅 코드에서 outputCapture
로 이 heumsi
와 skip
이 출력되었는지 체크할 수 있다.
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class) // SampleController 만 테스트
public class SampleControllerTest {
@MockBean
SampleService mockSampleService;
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception{
when(mockSampleService.getName()).thenReturn("heumsi_mock");
mockMvc.perform(get("/hello"))
.andExpect(content().string("hello heumsi_mock"));
// 해당 부분
assertThat(outputCapture.toString())
.contains("heumsi")
.contains("skip");
}
}
'더 나은 엔지니어가 되기 위해 > 지금은 안쓰는 자바' 카테고리의 다른 글
[스프링 부트 개념과 활용] 웹 MVC 설정 2. 정적 리소스와 웹 JAR (0) | 2020.02.05 |
---|---|
[스프링 부트 개념과 활용] 웹 MVC 설정 1. MessageConverters (0) | 2020.02.04 |
[스프링 부트 개념과 활용] 로깅 (0) | 2020.02.03 |
[스프링 부트 개념과 활용] Profile (0) | 2020.02.03 |
[스프링 부트 개념과 활용] 외부 설정 (0) | 2020.02.02 |