DevJong12

[Spring] Mock을 이용한 Servlet의 Request, Response테스트 방법 본문

Java,Spring/Spring

[Spring] Mock을 이용한 Servlet의 Request, Response테스트 방법

Jong12 2022. 8. 29. 20:45
728x90

개요

현재 기능 구현을 진행하면서 로그인을 임베디드된 톰캣에 세션으로 정보저장을 하고자 HttpServletRequest를 받아야 하는 메소드를 제작을 하였는데, 해당 기능에 대해서 테스트케이스를 제작을 하고 싶었다.

 

일반적인 Object의 경우라면 Mock을 사용하면 되지만 HttpServletRequest, Response도 가능한가? 생각하면서, 찾아서 알게된 MockHttpServletRequest와 MockHttpServletResponse에 대해서 기록하고자 한다.

 

참고한 자료는 아래의 북마크이다.

https://www.baeldung.com/java-httpservletrequest-mock

 

How to Mock HttpServletRequest | Baeldung

Learn multiple ways to mock a HttpServletRequest object when unit testing Servlet code in Java.

www.baeldung.com


개발환경

  • Mac
  • JDK11
  • Spring Boot 2.7.1
  • Spring MVC 5.3.21

Dependency

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.20</version>
    <scope>test</scope>
</dependency>

튜토리얼에는 위와같이 설정이 되어 있으나 굳이 추가를 할 필요는 없었다.

gradle의 testImplementation에서 Sprong-boot-starter-test가 포함이 되어있다면 아래의 사진처럼 Gradle의 파일들에서 확인해보면 좋을 듯 하다.

필자의 경우에는 Spring-boot-starter-test안에 spring-test가 참조를 하고있던 상황이라, 추가적인 설정은 필요 없었다.


사용 방법

선언을 해서 사용하는 방법은 아래 소스코드처럼 선언을 해서 사용을 하면 된다고 기록이 되어 있다.

mock으로 클래스를 넘겨주거나, 주입 혹은 MockHttp~로 객체를 생성하는 방법이 필자가 주로 사용하게 될 방법 같아 보였다.

  @Mock
  HttpServletRequest instanceMockRequest;

  @Test
  @DisplayName("MockHttpServletRequestTest")
  void test() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    HttpServletRequest mockRequest = mock(HttpServletRequest.class);
  }

 

추가적으로 아래의 사진처럼 org.springframework.mock으로 해서 응답에 대한 다양한 객체생성이 가능하니 추후 참고하면 좋을 듯 하다.

 

 

또한 어떻게 Mock선언이 가능한지 확인해보니 다음과 같이 상속받아 사용을 하고 있었다..


문제점

최초에 mock으로 생성을 할 때 필자의 경우 MockHttpServletRequest객체를 사용한게 아닌 mock(HttpServletRequest.class) 로 객체를 받아오려고 했었고 NPE가 지속적으로 발생하는 사소한(?) 문제가 있었다.

 

소스코드를 먼저 보여주고 설명을 이어가고자 한다.

 

테스트를 하고자 한 대상메소드

  @Override
  public ResponseDto userLogin(UserLoginDto userLoginDto, HttpServletRequest request) {
    try {
      String password = UserUtils.stringChangeToSha512(userLoginDto.getUserPassword());
      userLoginDto.setUserPassword(password);
    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
      throw new IllegalArgumentException(ExceptionMessage.SERVER_ERROR);
    }

    UserDto loginUser = userInfoMapper.selectUser(userLoginDto);

    if (ObjectUtils.isEmpty(loginUser)) {
      throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST);
    }

    ResponseDto result = ResponseEnum.OK.getResponse();
    if (loginUser.getUserRole() == UserRoleStatus.SUSPENSION_USER.getRoleValue()) {
      result.setResult("SUSPENSION_USER");
      return result;
    }

    if (loginUser.getUserRole() == UserRoleStatus.UNAUTH_USER.getRoleValue()) {
      result.setResult("UNAUTH_USER");
      return result;
    }

    HttpSession session = request.getSession();
    session.setAttribute(SessionEnum.USER_LOGIN.getValue(), loginUser);
    result.setResult(loginUser);
    return result;
  }

테스트 소스코드

  @Test
  @DisplayName("로그인 시 정상사용이 가능한 사용자인 경우, OK 상태값과 User정보를 획득한다.")
  void userLogin_WhenAuthUserThenOkAndUserInfo() {
    //given
    HttpServletRequest request = mock(HttpServletRequest.class);

    UserLoginDto mockUserLoginDto = new UserLoginDto();
    mockUserLoginDto.setUserEmail("test@Email.com");
    mockUserLoginDto.setUserPassword("testPassword");

    UserDto mockReturnDto = new UserDto();
    mockReturnDto.setUserId(UUID.randomUUID().toString());

    mockReturnDto.setUserRole(UserRoleStatus.AUTH_USER.getRoleValue());

    //mock
    when(userInfoMapper.selectUser(mockUserLoginDto)).thenReturn(mockReturnDto);

    //when
    ResponseDto result = userInfoService.userLogin(mockUserLoginDto, request);

    //then
    assertEquals(result.getResultCode(), HttpStatus.OK.value());
    assertEquals(!ObjectUtils.isEmpty((UserDto) result.getResult()), true);
  }

실행결과

java.lang.NullPointerException
at com.nooblol.community.service.impl.UserInfoServiceImpl.userLogin(UserInfoServiceImpl.java:101)
at com.nooblol.community.service.impl.UserInfoServiceImplTest.userLogin_WhenAuthUserThenOkAndUserInfo(UserInfoServiceImplTest.java:190)

 

실행결과의 경우 최상위 부분만 자른 것이며, 결론은 NPE다. 왜?

 

필자의 테스트를 진행하는 메소드에는 HttpServletRequest.getSession()메소드를 실행한다.

mock(HttpServletRequest.class)로 선언한 HttpServletRequest는 getSession(true)로 생성하더라도 Null을 Return한다.

그리고 필자는 NPE처리를 진행하지 않아서 오류가 발생했다.

 

Session에서 Null이 반환될줄은 생각을 못했었다. 


해결 방법

mock을 통한 객체 생성을 하는 경우, Test케이스 제작시에 메소드에서 사용하는 경우 HttpServlet에서 사용하는 객체를 모두 생성해 줘야 한다.

문제점에 있던 테스트 케이스를 수정해보면 아래와 같이 수정할 수가 있겠다.

필자의 경우 getSession()메소드만 사용을 하고 있기에 다음과 같은 경우를 추가하였다.

    HttpServletRequest request = mock(HttpServletRequest.class);
    HttpSession session = mock(HttpSession.class);
    when(request.getSession()).thenReturn(session);

request.getSession()을 하면 빈 session을 Return하도록 소스를 추가하였다.

 

parameter에 대한 return 이 필요하면 다음과 같이 제작을 하면된다.

    when(request.getParameter("hello")).thenReturn("Hi");

필자의 경우, 아 그럼 테스트케이스를 제작시마다 세션을 사용하면 계속 생성해줘야하나?

그건 좀 불편할거같은데? 라는 생각을 하고 튜토리얼에 있던 다른 방법은 MockHttpServletRequest에 대해서 활용을 해봤다.

 

다음과 같이 소스코드를 제작해봤으며 given에서 굳이 HttpSession에 대해서 필수적으로 제작할 필요가 없어지게 되었다.

Return값이 필요한 경우면 추가적인 제작을 해줘야 겠지만 빈객체가 와도 상관이 없다면 Mock~ 객체를 사용해보는건 어떨까 싶다.

  @Test
  @DisplayName("로그인 시 정상사용이 가능한 사용자인 경우, OK 상태값과 User정보를 획득한다.")
  void userLogin_WhenAuthUserThenOkAndUserInfo() {
    //given
    MockHttpServletRequest request = new MockHttpServletRequest();

    UserLoginDto mockUserLoginDto = new UserLoginDto();
    mockUserLoginDto.setUserEmail("test@Email.com");
    mockUserLoginDto.setUserPassword("testPassword");

    UserDto mockReturnDto = new UserDto();
    mockReturnDto.setUserId(UUID.randomUUID().toString());

    mockReturnDto.setUserRole(UserRoleStatus.AUTH_USER.getRoleValue());

    //mock
    when(userInfoMapper.selectUser(mockUserLoginDto)).thenReturn(mockReturnDto);

    //when
    ResponseDto result = userInfoService.userLogin(mockUserLoginDto, request);

    //then
    assertEquals(result.getResultCode(), HttpStatus.OK.value());
    assertEquals(!ObjectUtils.isEmpty((UserDto) result.getResult()), true);
  }

 

※추가적으로 MockHttpServlet~을 이용할 경우 when을 통한 케이스 제작이 아닌 아래의 소스코드 방식으로 제작을 하면 된다.

    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setParameter("hello", "hi");
    request.setSession(null);

 

728x90
Comments