개발환경
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 에서 발생한 오류까지 모두 내가원하는대로 리턴되는 결과를 얻을 수 있었다.