RestController 테스트

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";
    }
}

BasicServicegreetingMessage라는 메서드를 가지고 있는데요. 간단하게 input"hi"인 경우에 "ok, hi" 라고 응답하고, "hello"인 경우에 "ok, hello"로 응답하며, "hi", "hello"에 해당하지 않는 경우는 "ok"로 응답하는 메서드입니다.

이렇게 코드를 작성한 뒤에 다시 테스트를 실행해 보면, 성공하는 것을 볼 수 있습니다.

※ 참고로 위와 같이 실패하는 테스트를 먼저 작성하고 나서 프로덕션 코드를 작성하는 방식으로 점진적으로 개발하는 방법을 TDD(Test Driven Development) (👈 click!!) 라고 합니다. 이런 방식으로 반복해서 테스트 코드를 추가하다 보면 나중에는 명령 한번으로 전체적인 코드를 테스트 할 수 있게 됩니다. 코드 수정으로 인해서 다른 부분에 버그가 생겼다면 테스트 코드가 버그를 잡아줄 수 있어요. 물론 테스트 코드는 여러가지 케이스에 대해서 촘촘하게 작성해 줄수록 좋습니다. 개발자가 직접 여러 가지 경우를 수동으로 테스트 할 필요가 없어지게 되므로 많은 시간을 아낄 수 있게 되고, 성공시켜야 할 테스트를 먼저 작성함으로써 개발 작업의 단위를 명확하게 할 수 있어서 좋습니다.

MockMvc로 RestController를 테스트 하는 방법에 대해서 간단하게 알아보았습니다. 기본적인 테스트 방법을 알게 되셨으니 관련 도서를 보거나 인터넷 검색을 통해서 필요한 부분을 배워 나가시는 데 도움이 될 거라고 생각합니다. 만약 Junit에 대해서 모르신다면, 이 글 (👈 click!!)을 한 번 읽어보세요!

스스로 경험하며 얻은 깨달음을 공유하기 좋아하며, 세상이 필요로 하는 코드를 작성하기 위해 노력하는 개발자입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다