목차
RestController 테스트에 대해서 왜 알아둬야 할까요? 조금 공부해두면 여러분의 시간을 아낄 수 있는 무기로 사용할 수 있기 때문입니다. MockMvc을 활용한 RestController 테스트 방법을 익히면, REST API 개발 시 서버를 띄우고 직접 URL을 입력하여 요청을 보내고 응답을 눈으로 일일이 확인하는 일을 자동화할 수 있어요. 테스트를 자동화 하지 않으면 수동으로 테스트를 반복하게 됩니다. 조금만 장기적인 관점으로 보면 테스트 코드를 작성해서 테스트를 자동화하는 것이 효율적인 선택이라는 것을 알 수 있어요. MockMvc로 RestController 테스트를 자동화하는 방법에 대해서 간단하게 알아보는 시간을 가져보시길 권합니다.
테스트 코드를 작성하는 이유 (👈 click!!)
Spring MVC에서 Dispatcher Servlet 객체는 HTTP 통신의 출입구 역할을 합니다. Dispatcher Servlet이 요청을 받으면 해당 요청을 처리할 수 있는 컨트롤러를 찾아서 요청을 전달합니다. 요청을 전달 받은 컨트롤러가 적절한 로직을 수행하고 응답을 돌려주는 방식으로 동작하죠.
이런 과정을 테스트하려면 요청이 들어오고 응답을 돌려주는 환경 그 자체를 대신할 무언가가 필요합니다. MockMvc가 바로 그 역할을 해줘요. 한마디로 MockMvc란 Spring MVC 테스트를 위한 껍데기를 만들어 놓은 겁니다. 특정 HTTP 요청을 받았을 때 어떤 응답을 기대하는지 MockMvc에 세팅 해 두고, 테스트를 실행하면 기대 값과 일치하는지 일치하지 않는지 알려줍니다.
Spring에서 제공하는 MockMvc 클래스에 대한 문서를 여기에서 볼 수 있습니다.
RestController 테스트
기본적인 RestController 테스트 코드를 작성해보기 위해서 "ok"
를 반환해주는 RestController
를 구현합니다.
RestController 예시
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BasicRestController {
@GetMapping("/ok")
public String ok() {
return "ok";
}
}
테스트 예시
GET /ok
를 호출했을 때 "ok"
를 boby에 담아서 응답해주는지 확인하는 테스트를 작성해봤습니다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
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.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@AutoConfigureMockMvc
@SpringBootTest
class BasicRestControllerTest {
@Autowired
private MockMvc mockMvc;
@DisplayName("ok 문자열을 body 에 담아서 응답한다.")
@Test
void ok() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/ok"))
.andExpectAll(
MockMvcResultMatchers.status().isOk(),
MockMvcResultMatchers.content().string("ok")
);
}
}
RestController 테스트(with business logic)
이번에는 테스트 코드부터 작성해 보겠습니다. 어떻게 동작하기를 원하는지 생각하면서 테스트 코드를 먼저 작성해 보는 거예요.
테스트 예시(with business logic)
@DisplayName("input 이 \"hello\" 라면 \"ok, hello\" 를 응답한다.")
@Test
void okHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/ok")
.queryParam("input", "hello"))
.andExpectAll(
MockMvcResultMatchers.status().isOk(),
MockMvcResultMatchers.content().string("ok, hello")
);
}
@DisplayName("input 이 \"hi\" 라면 \"ok, hi\" 를 응답한다.")
@Test
void okHi() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/ok")
.queryParam("input", "hi"))
.andExpectAll(
MockMvcResultMatchers.status().isOk(),
MockMvcResultMatchers.content().string("ok, hi")
);
}
이렇게 작성한 뒤에 테스트를 실행해 보면 당연히 실패하게 됩니다. 지금은 항상 "ok"
를 응답하고 있으니까요.
실패하는 테스트를 성공시키기 위해서 BasicRestController
를 수정해 보겠습니다.
RestController 예시(with business logic)
import com.targetcoders.mockmvctest.service.BasicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BasicRestController {
private final BasicService basicService;
@Autowired
public BasicRestController(BasicService basicService) {
this.basicService = basicService;
}
@GetMapping("/ok")
public String ok(@RequestParam(value = "input", required = false, defaultValue = "") String input) {
return basicService.greetingMessage(input);
}
}
요청에 포함된 input
에 따라서 다른 응답을 주기 위해 BasicService
클래스를 추가했습니다.
import org.springframework.stereotype.Service;
@Service
public class BasicService {
public String greetingMessage(String input) {
if (input.equals("hi")) {
return "ok, hi";
} else if (input.equals("hello")) {
return "ok, hello";
}
return "ok";
}
}
BasicService
는 greetingMessage
라는 메서드를 가지고 있는데요. 간단하게 input
이 "hi"
인 경우에 "ok, hi"
라고 응답하고, "hello"
인 경우에 "ok, hello"
로 응답하며, "hi"
, "hello"
에 해당하지 않는 경우는 "ok"
로 응답하는 메서드입니다.
이렇게 코드를 작성한 뒤에 다시 테스트를 실행해 보면, 성공하는 것을 볼 수 있습니다.
※ 참고로 위와 같이 실패하는 테스트를 먼저 작성하고 나서 프로덕션 코드를 작성하는 방식으로 점진적으로 개발하는 방법을 TDD(Test Driven Development) (👈 click!!) 라고 합니다. 이런 방식으로 반복해서 테스트 코드를 추가하다 보면 나중에는 명령 한번으로 전체적인 코드를 테스트 할 수 있게 됩니다. 코드 수정으로 인해서 다른 부분에 버그가 생겼다면 테스트 코드가 버그를 잡아줄 수 있어요. 물론 테스트 코드는 여러가지 케이스에 대해서 촘촘하게 작성해 줄수록 좋습니다. 개발자가 직접 여러 가지 경우를 수동으로 테스트 할 필요가 없어지게 되므로 많은 시간을 아낄 수 있게 되고, 성공시켜야 할 테스트를 먼저 작성함으로써 개발 작업의 단위를 명확하게 할 수 있어서 좋습니다.
MockMvc로 RestController를 테스트 하는 방법에 대해서 간단하게 알아보았습니다. 기본적인 테스트 방법을 알게 되셨으니 관련 도서를 보거나 인터넷 검색을 통해서 필요한 부분을 배워 나가시는 데 도움이 될 거라고 생각합니다. 만약 Junit에 대해서 모르신다면, 이 글 (👈 click!!)을 한 번 읽어보세요!