Dev/TDD

MockMvc 을 사용해서 Controller 테스트 코드 작성하기

린네의 2024. 1. 29. 21:26

 

 

개발환경

IDE :  intelliJ 
FrameWork : springboot 3.2.2
Launguage java 17
DB : h2
Build Tool : Gradle

 

 

 

 

1. Controller 테스트 하기

 

Controller 작성 후 테스트할 때 MockMvc 를 이용하면 정상적으로 내가 원하는 요청을 넣었을 때 응답이 리턴되는지 확인 할 수 있다.

 

사용 방법은 다음과 같다.

 

@AutoConfigureMockMvc
@SpringBootTest

    @Autowired
    MockMvc mvc;

    @Autowired
    TestService service;
    
    
       @Test
    void testExample() throws Exception {
    
    	...
    
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
            
            
        //  then
        this.mvc.perform(get("/sboot/vehicle")
        				.accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
            
            
            
       this.mvc.perform(post("/sboot/vehicle2")
                        .contentType(MediaType.APPLICATION_JSON)
                        .characterEncoding("UTF-8")
                        .content(requestData))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value(DUPLICATED_DATA.name()))
                .andDo(print());
            
    }

 

 

 

  •  perform   :  실제로 API 호출시 사용하는 mappingUrl 을 삽입하여 호출테스트를 진행할 수 있다.  get, post 방식 모두 제공한다. post 방식을 사용할 땐 requestBody 를 jsonstring 형태등으로 선언하여 content 를 세팅하는것도 가능하다.
  •  andExpect : URL 을 호출했을 때 오는 상태코드나 리턴값을 확인할 수 있다. JsonPath 등을 이용하여 좀더 세분화 된 값 확인도 가능하다

 

이 때 주의 할점이 @AutoConfigureMockMvc 가 무엇인지 확실하게 알고 있어야 한다.  Mock 을 사용해서 테스트를 진행할 때 함께 많이 나오는 내용이 @WebMvcTest 인데 둘의 차이는  공식 레퍼런스 를 참고하는게 좋다.

 

 

나는 @AutoCofigureMockMvc 를 사용했는데, 해당 애노테이션은 @Controller, @Repository, @Service  에 대해 모두 자동으로 등록해준다. 

 

 

 

아래부터는 내가 처음 MocMvc 를 사용할 때 겪었던 문제 내용들이다.

 

 

1.  1차 시도 :  @WebMvcTest 와 @MockBean 사용하기 

 

컨트롤러가 잘 돌아가는지 확인하기 위한 방법이 없는지 검색하다가 , mock 이라는 내용을 알게 되었다.  구글 검색하면 나오는 블로그에 있는 수많은 참고자료와 공식홈페이지에서 보니 정말 좋아보였다. 

 

그래서  처음에  service 등록시 공식 레퍼런스에서 제공하는

 

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserVehicleController.class)
class MyControllerTests{

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }

}

 

를 참고해서 적용했는데, 이렇게 하니까내 코드에서는 아예 테스트 코드가 오류가 나면서 안돌아 갔다.정확히는 service 에 있는 함수를 given 에 주려고 하니까 실행이 안됐다. 아 ! 뭔가 내가 원하는데로 내부에서 주입이 제대로 안되고 있다는 생각이 들었다.

 

 

 

2.  2차 시도 :  @AutoConfirueMockMvc 와 @MockBean 사용하기 

 

그래서 다시 어찌저찌 방법을 찾아보니@AutoConfigureMockMvc 라는게 있는것이다.  이 애노테이션을 사용하면 자동으로 @Component 를 다 선언해준다는게아닌가? @WebMvcTest 를 제거하고 @AutoConfigureMockMvc 로 변경해서 아래와 같이 코드를 수정 했다.

 

 수정한 코드는 다음과 같다.

 

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest
class MyControllerTests{

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }

}

 

 

이렇게하니까 안돌아가던 코드가 돌아는 갔다. 그런데 , service 랑 repository 에서 발생해야할 예외상황에서도 내가 지정한  ResponseStatus 가 아니라 200이 떨어졌다. 오류가 나야하는데 오류가 나지 않는 .. 내가 지정한건 BadRequest 였는데 자꾸 Ok 가 리턴 됐다. Controller 에서 발생하는 에러는 정상적으로 발생 되었기 때문에 처음엔은 ExceptionHandler 를 의심했다.

 

3.  3차 시도 :  테스트 실행전에 @ControllerAdvice 세팅해주기 


이건 지금 생각하면 DB 로그도 남지 않았고 서비스 로그도 남지 않았기 때문에 전혀 시도할 내용이 아닌데 왜 시도해본지 모르겠지만... Excetpion 이 발생하지 않는다는 부분에 포커스가 맞춰져서 (  참고링크  )

 

테스트 실행전에 ExceptionHandler 를 먼저 등록했다. 

 

 @BeforEach
    void setUp() {
        SampleController controller = new SampleController();

        mockMvc = MockMvcBuilders
            .standaloneSetup(controller)
            .setControllerAdvice(ExcpetionHandlerAdvice.class)  // ExceptionHandler 등록
        .build();
    }

 

당연히 원하는 결과는 나오지 않았고 이 코드는 삭제 했다

 

 

4.  4차 시도 :  서비스 호출시 @Autowired 로 변경 

 

컨트롤러 테스터를 하다가 원하는 결과가 나오지 않아 refresh 하는 느낌으로 서비스단 단위테스트를 진행하다보니 .. 아 이게 내가 원하는대로 빈주입이 안되어서 그렇구나 라는 생각이 들었다. 그래서 @AutoConfigureMocMvc 의 역할을 다시 찾아봤다.  그리고나서 service  를 @Autowired 를 사용해서 선언했다.

 

// given
등록 데이터 코드

//  when, then
String requestData = objectMapper.writeValueAsString(member);

mvc.perform(post("/회원가입")
                .contentType(MediaType.APPLICATION_JSON)
                .characterEncoding("UTF-8")
                .content(requestData))
        .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.code").value(INVALID_REGIST_USER.name()))
        .andDo(print());



 

그랬더니 정상적으로 Service, Repository 에서 발생한 오류까지 모두 내가원하는대로 리턴되는 결과를 얻을 수 있었다.