5-4. 회원 기능 통합 테스트
MockMVC
Controller에 HTTP 요청을 만들어 전송하기 위해서 MockMVC를 사용해 주었다.
테스트 클래스에 다음의 두 어노테이션을 추가해 준다.
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigMockMVC는 객체를 JSON타입으로, JSON 타입을 객체로 변환해 주기 위한 ObjectMapper와 임의의 HTTP 요청을 만들어 줄 MockMVC를 사용하기 위해 추가한다.
통합 테스트를 진행하기 위해 각각의 스프링 빈은 의존관계 주입이 진행되어야 하기에 @SpringBootTest를 사용해 주었다.
만약 @SpringBootTest를 사용하지 않으면 @Autowired를 사용해 UserController 객체를 가지고 오더라도 해당 객체 내부에 존재하는 UserService 객체가 제대로 주입되지 않을 수 있다.
이어서, MockMVC의 요청을 만들거나 결과를 편하게 확인하기 위해 다음의 두 가지 클래스를 static import 한다.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
이제 다음과 같이 HTTP 요청을 만들고, 결과를 받을 수 있다.
MvcResult result = mockMvc.perform(post(joinUrl)
.param("paramKey", "paramValue"))
.andExpect(status().isOk())
.andReturn();
MockMvc.perform() 메서드 내부에서 요청을 정의한 후, andExpect() 메서드로 응답 결과를 확인한다.
andReturn을 통해 결과를 MvcResult 객체로 받아와 더 자세한 결과 검증을 진행할 수 있다.
유저 저장 성공 테스트
다음은 유저 저장 성공 테스트이다.
@Test
@Transactional
public void saveUser() throws Exception {
WebUser user = new WebUser("new User", "userID", "userPW");
//given
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", user.getUserName())
.param("id", user.getId())
.param("password", user.getPassword()))
.andExpect(status().is3xxRedirection())
.andReturn();
//when
MockHttpServletResponse response = result.getResponse();
//then
assertThat(response.getRedirectedUrl()).matches("/user/[0-9]+");
}
유저의 저장 성공의 경우 redirect 되는 URL의 패턴을 확인하는 것으로 동작을 확인하였다.
RequestBuilder 사용법 - 데이터 전송
<form> 태그 요청에 대한 정리는 위 포스팅에 정리해 두었으니 궁금하면 참고하자.
유저 저장 실패 테스트
유저 저장 실패의 경우 Model에 경고 메시지를 담고, 다시 유저 저장 페이지로 돌아가도록 하였다.
때문에 이 경우 andReturn으로 반환되는 결과는 타임리프의 바인딩 이후 결과인 VIew 자체가 반환된다.
그래서 이번엔 MockMvcHttpResponse 객체 대신 ModelAndView 객체를 받아 Warnings를 확인해 본다.
@BeforeEach
public void existingUser() throws Exception {
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", extUser.getUserName())
.param("id", extUser.getId())
.param("password", extUser.getPassword())
)
.andReturn();
extIdx = Integer.parseInt(result.getResponse()
.getRedirectedUrl()
.replace("/user/", ""));
}
@Test
@Transactional
public void duplicatedUserSave() throws Exception {
//given
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", extUser.getUserName())
.param("id", extUser.getId())
.param("password", extUser.getPassword()))
.andReturn();
//when
ModelAndView mv = result.getModelAndView();
//then
assertThat(mv.getViewName()).isEqualTo("user/join");
Map<String, Object> modelAttributes = mv.getModel();
assertThat(modelAttributes.get("warnings"))
.isEqualTo(WarningFactory.duplicatedUserIdWarnings);
}
또, Warning 객체를 편하게 생성하고, 비교하기 위해 static Warning 객체를 반환하는 WarningFactory를 다음과 같이 생성하였다.
public class WarningFactory {
public static Warning duplicatedUserIdWarnings = new Warning("중복된 회원 ID 입니다.");
public static Warning noUserFindWarnings = new Warning("해당 회원이 존재하지 않습니다");
public static Warning cantModifyWarnings = new Warning("ID는 수정할 수 없습니다.");
public static void addWarnings(Model model, Exception e){
Warning warning = null;
Class<? extends Exception> exceptionClass = e.getClass();
if(exceptionClass.equals(NoSuchUserException.class)) {
warning = noUserFindWarnings;
}
else if(exceptionClass.equals(DuplicatedUserIdException.class)) {
warning = duplicatedUserIdWarnings;
}
else if(exceptionClass.equals(CantModifyFieldException.class)) {
warning = cantModifyWarnings;
}
if(warning ==null) {
return;
}
model.addAttribute("warnings", warning);
}
}
이제 컨트롤러에서는 Exception 상황에 대해 WarningFactory의 addWarnings를 사용하면 된다.
아래는 ControllerTest의 전문이다.
package zangsu.selfmadeBlog.user.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.web.servlet.ModelAndView;
import zangsu.selfmadeBlog.model.web.WarningFactory;
import zangsu.selfmadeBlog.user.controller.model.WebUser;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
final String joinUrl = "/user/join";
long extIdx;
final static WebUser extUser = new WebUser("extUser", "extUserID", "extUserPassword");
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@BeforeEach
public void existingUser() throws Exception {
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", extUser.getUserName())
.param("id", extUser.getId())
.param("password", extUser.getPassword())
)
.andReturn();
extIdx = Integer.parseInt(result.getResponse()
.getRedirectedUrl()
.replace("/user/", ""));
System.out.println(extIdx);
}
@Test
@Transactional
public void saveUser() throws Exception {
WebUser user = new WebUser("new User", "userID", "userPW");
//given
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", user.getUserName())
.param("id", user.getId())
.param("password", user.getPassword()))
.andExpect(status().is3xxRedirection())
.andReturn();
//when
MockHttpServletResponse response = result.getResponse();
//then
assertThat(response.getRedirectedUrl()).matches("/user/[0-9]+");
}
@Test
@Transactional
public void duplicatedUserSave() throws Exception {
//given
MvcResult result = mockMvc.perform(post(joinUrl)
.param("userName", extUser.getUserName())
.param("id", extUser.getId())
.param("password", extUser.getPassword()))
.andReturn();
//when
ModelAndView mv = result.getModelAndView();
//then
assertThat(mv.getViewName()).isEqualTo("user/join");
Map<String, Object> modelAttributes = mv.getModel();
assertThat(modelAttributes.get(WarningFactory.WarningKey))
.isEqualTo(WarningFactory.duplicatedUserIdWarnings);
}
@Test
@Transactional
public void findUserSuccess() throws Exception{
//given
MvcResult result = mockMvc.perform(get("/user/" + extIdx))
.andExpect(status().isOk())
.andReturn();
//when
ModelAndView mv = result.getModelAndView();
ModelMap modelMap = mv.getModelMap();
//then
assertThat(modelMap.containsKey("user")).isTrue();
WebUser user = (WebUser) modelMap.get("user");
assertThat(user.getUserName()).isEqualTo(extUser.getUserName());
assertThat(user.getId()).isEqualTo(extUser.getId());
assertThat(user.getPassword()).isEqualTo(extUser.getPassword());
}
@Test
@Transactional
public void findUserFail() throws Exception{
//given
MvcResult result = mockMvc.perform(get("/user/" + (extIdx + 1)))
.andExpect(status().isOk())
.andReturn();
//when
ModelAndView mv = result.getModelAndView();
Map<String, Object> models = mv.getModel();
//then
assertThat(models.containsKey(WarningFactory.WarningKey)).isTrue();
assertThat(models.get("warnings")).isEqualTo(WarningFactory.noUserFindWarnings);
}
@Test
@Transactional
public void modifyUserSuccess() throws Exception{
//given
MvcResult result = mockMvc.perform(post("/user/" + extIdx)
.param("userName", "newUserName")
.param("id", extUser.getId())
.param("password", "newPassword"))
.andExpect(status().isOk())
.andReturn();
//when
Map<String, Object> models = result.getModelAndView().getModel();
//then
assertThat(models.containsKey("user")).isTrue();
WebUser user = (WebUser) models.get("user");
assertThat(user.getUserName()).isEqualTo("newUserName");
assertThat(user.getPassword()).isEqualTo("newPassword");
}
@Test
@Transactional
public void modifyUserFail() throws Exception{
//given
MvcResult result = mockMvc.perform(post("/user/" + extIdx)
.param("id", "newUserId"))
.andExpect(status().isOk())
.andReturn();
//when
ModelAndView mv = result.getModelAndView();
Map<String, Object> models = mv.getModel();
//then
assertThat(models.containsKey(WarningFactory.WarningKey)).isTrue();
assertThat(models.get(WarningFactory.WarningKey)).isEqualTo(WarningFactory.cantModifyWarnings);
}
@Test
@Transactional
public void deleteUserSuccess() throws Exception{
//given
MvcResult result = mockMvc.perform(delete("/user/" + extIdx))
.andExpect(status().isOk())
.andReturn();
//when
//then
}
}