[SpringBoot] 컨트롤러를 테스트 하는 방법 - @WebMvcTest, @MockBean
컨트롤러 테스트 클래스
컨트롤러를 테스트 하기 위해서는 @WebMvcTest와 @MockBean를 사용할 수 있다.
@WebMvcTest를 이용해서 컨트롤러를 띄울 수 있고,
@MockBean을 통해서 가짜 빈 (이 자체로는 그냥 껍데기만 있는 빈)을 컨테이너에 등록할 수 있다.
추가로 테스트에 필요한 스프링 빈들은 (MockMvc나 ObjectMapper)은 @AutoWired로 주입받는다.테스트 클래스안의 @Test 메소드 - given
Mockito에서 제공하는 given( ) 메소드를 이용해서 mockBean을 설정해줄 수 있다.
예를들어, accountService라는 목빈을 @MockBean으로 선언한 상태에서,
accountService의 acreateAccount가 리턴하는 값을 설정하고 싶다면, 아래와 같이 willReturn을 통해 설정할 수 있다.
@MockBean
private AccountService accountService;
@Test
void successCreateAccount() throws Exception {
//given - 어떤 매개변수를 받더라도 createAccount가 리턴하는 AccountDto가 이렇다.
//createAccount가 리턴하는 것이 Account(엔티티)인지, AccountDto인지 정확히 이해해야 아래 코드를 짤 수 있음
given(accountService.createAccount(anyLong(), anyLong()))
.willReturn(AccountDto.builder()
.userId(1L)
.accountNumber("1234567890")
.registeredAt(LocalDateTime.now())
.ubregisteredAt(LocalDateTime.now())
.build());
}
- 테스트 클래스안의 @Test 메소드 - then(요청 보내기)
컨트롤러에 응답을 주기 위해 미리@AutoWired
로 선언해둔 mockMvc인스턴스를 이용한다.
mockMvc의 perform( )함수를 이용하면, 어떤 방법으로 URL을 넘겨줄 지 설정할 수 있다.
e.g.mockMvc.perform(post("/account")
또한 이 뒤에 빌더 방법으로 .contentType( ) 메소드로는 Http 헤더를 채울 수 있고,
.content( ) 메소드로는 Http 바디를 채울 수 있다.
이때 주의할 것은, http 바디에 들어가는 json을 입력하는 방법이 두가지 있다는 것이다.
첫번째 방법은 문자열을 json 형식으로 넣어주는 것이고,
두번째 방법은 objectMapper의 writeValueAsString을 이용하는 것이다.
objectMapper은 자바 객체를 json 형태로 바꿔주고, 그 역도 가능하게 해준다.
objectMapper의 인자로 미리 만들어준 Request 객체를 넣어주면,
'이런 URL과 이런 method로, 이런 헤더를 달고, 이런 바디(json)을 가진 요청을 한다'라는 뜻이 된다.
@MockBean
private AccountService accountService;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void successCreateAccount() throws Exception {
//given - 어떤 매개변수를 받더라도 createAccount가 리턴하는 AccountDto가 이렇다.
given(accountService.createAccount(anyLong(), anyLong()))
.willReturn(AccountDto.builder()
.userId(1L)
.accountNumber("1234567890")
.registeredAt(LocalDateTime.now())
.ubregisteredAt(LocalDateTime.now())
.build());
//when
//then - 그러면 account Post를 했을 때 이런 요청을 할 것이다.
mockMvc.perform(post("/account")
.contentType(MediaType.APPLICATION\_JSON)
.content(objectMapper.writeValueAsString( // 이 new 객체를 json형태로 바꿔서 넣어줌
new CreateAccount.Request(1L, 100L) // 사실 anyLong이기 때문에 여기 값은 상관 x
)))
}
- 테스트 클래스안의 @Test 메소드 - then(응답 테스트 하기)
mockMvc.perform( )의 결과에 andExpect 메소드를 연결하여 결과가 예상한 것이 나오는지를 테스트할 수 있다.
예를들어, `.andExpect(status().isOk())`는 응답이 잘 이루어졌는지를 검사하고,
`.andExpect(jsonPath("$.userId").value(1))`는 응답의 http 바디에 있는 json 중 jsonPath에 해당하는 값(=userId의 값)이 1인지를 검사하는 식이다.
우리 테스트의 경우, given을 통해서 '어떤 요청이 들어오더라도, Service의 createAccount가 리턴하는 것은
userId가 1L이고, accountNumber가 "1234567890"인 accountDto라고 설정을 했으므로,
이를 검사하면 '컨트롤러가 서비스가 리턴하는 값을 요청으로 잘 보내주는지'를 테스트할 수 있는 것이다.
전체 코드 :
package com.example.account.controller;
import com.example.account.domain.Account;
import com.example.account.dto.AccountDto;
import com.example.account.dto.CreateAccount;
import com.example.account.type.AccountStatus;
import com.example.account.service.AccountService;
import com.example.account.service.RedisTestService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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 java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(AccountController.class)
class AccountControllerTest {
@MockBean
private AccountService accountService;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void successCreateAccount() throws Exception {
//given - 어떤 매개변수를 받더라도 createAccount가 리턴하는 AccountDto가 이렇다.
given(accountService.createAccount(anyLong(), anyLong()))
.willReturn(AccountDto.builder()
.userId(1L)
.accountNumber("1234567890")
.registeredAt(LocalDateTime.now())
.ubregisteredAt(LocalDateTime.now())
.build());
//when
//then - 그러면 account Post를 했을 때 이런 요청을 할 것이다.
mockMvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString( // 이 new 객체를 json형태로 바꿔서 넣어줌
new CreateAccount.Request(1L, 100L) // 사실 anyLong이기 때문에 여기 값은 상관 x
)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.userId").value(1))
.andExpect(jsonPath("$.accountNumber").value("1234567890"))
.andDo(print());
}
}
}