프로젝트/selfmade Blog - V2

5-4. 회원 기능 통합 테스트

zangsu_ 2023. 9. 28. 20:39

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 사용법 - 데이터 전송

 

https://zangsu.tistory.com/59

 

MockMVC를 사용한 Form 데이터 전송

이번 글에서 MockMVC를 사용해 Form 데이터를 전송하는 요청을 만들어 보자. 태그 데이터의 전송 MockMVC를 알아보기 전에 Form 태그의 데이터 전송 방법을 먼저 알아보자. Http 통신에서 데이터는 파라

zangsu.tistory.com

 

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