-
[Spring Boot] mockMvc를 통해 담겨오는 HttpOnly Cookie 내용물 검증하는 테스트 코드 작성법Spring/Study 2023. 7. 23. 23:10
JWT 토큰을 사용하여 로그인을 하면 Access토큰과 Refresh토큰을 응답으로 주는데, 이때 Refresh토큰을 HttpOnly 쿠키로 전달하는 상황이다.
프론트 측에서는 담겨오는 쿠키를 임의로 설정할 수 없으므로 좀 더 보안에 유리하다고 생각한다.
개발 단계에서 이를 검증하기 위해서 mockMvc를 통해 테스트 코드를 작성하는데 body에 담겨오는 값들은 jsonPath를 통해 검증을 진행할 수 있지만,
헤더에 담겨오는 HttpOnly 쿠키는 따로 옵션이 설정되어 값 + 옵션 값까지 동시에 전달된다.
그렇다면 테스트 코드를 어떻게 작성을 해야 하는가? 에 대한 해결 방법이다.
가장 먼저 테스트 코드는 given when then 패턴으로 작성하였다.
when까지의 패턴 코드이다
when 까지의 코드
@Test @WithMockUser @DisplayName("어드민 로그인 성공 테스트") void successAdminLogin() throws Exception { // given given(memberService.loginAdmin(any())).willReturn(member); given(jwtProvider.createAccessToken(any())).willReturn("ACCESS_TOKEN"); given(jwtProvider.createRefreshToken(any())).willReturn("REFRESH_TOKEN"); ResponseCookie responseCookie = ResponseCookie.from("refreshToken", "REFRESH_TOKEN") .httpOnly(true) .secure(true) .sameSite("None") .path("/") .maxAge(3600000) .build(); given(memberService.createHttpOnlyCookie(any())).willReturn(responseCookie); // when ResultActions resultActions = mockMvc.perform(post("/admin/login") .contentType("application/json") .with(csrf()) .content(objectMapper.writeValueAsString(adminLoginRequest))); ... }
여기서 눈 여겨볼 부분은 ResponseCookie 클래스를 만드는 과정이다. 옵션 값들이 따로 지정되어 있고, /admin/login이라는 경로로 요청을 할 경우
헤더에는 쿠키로 저장된 refreshToken과 accessToken 그리고 Body에는 따로 지정한 DTO 클래스가 반환될 것이다.실패했던 코드
@Test @WithMockUser @DisplayName("어드민 로그인 성공 테스트") void successAdminLogin() throws Exception { // given given(memberService.loginAdmin(any())).willReturn(member); given(jwtProvider.createAccessToken(any())).willReturn("ACCESS_TOKEN"); given(jwtProvider.createRefreshToken(any())).willReturn("REFRESH_TOKEN"); ResponseCookie responseCookie = ResponseCookie.from("refreshToken", "REFRESH_TOKEN") .httpOnly(true) .secure(true) .sameSite("None") .path("/") .maxAge(3600000) .build(); given(memberService.createHttpOnlyCookie(any())).willReturn(responseCookie); // when ResultActions resultActions = mockMvc.perform(post("/admin/login") .contentType("application/json") .with(csrf()) .content(objectMapper.writeValueAsString(adminLoginRequest))); // then resultActions .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.SET_COOKIE, responseCookie.getValue())) .andExpect(header().string("accessToken", "ACCESS_TOKEN")) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.admin").value(true)) .andExpect(jsonPath("$.writer").value(true)) .andDo(print()); }
처음에는 단순하게 HttpHeaders.Set_COOKIE의 값을 단순 비교하였다.
그러나 예상값과 다르게 실제 값은 옵션값 까지 전부 포함되어 있었다.
Expected :REFRESH_TOKEN Actual :refreshToken=REFRESH_TOKEN; Path=/; Max-Age=3600000; Expires=Sun, 3 Sep 2023 05:49:05 GMT; Secure; HttpOnly; SameSite=None
여기서 어떤 메소드를 사용하면 refreshToken이라는 이름의 값만 비교할 수 있을까를 고민을 하다가
람다식을 사용하여 ResultAction 방식 대신에 assertj를 사용하는 방식을 생각했다.
그리하여 작성한 실제코드는 다음과 같다.
성공 코드
@Test @WithMockUser @DisplayName("어드민 로그인 성공 테스트") void successAdminLogin() throws Exception { // given given(memberService.loginAdmin(any())).willReturn(member); given(jwtProvider.createAccessToken(any())).willReturn("ACCESS_TOKEN"); given(jwtProvider.createRefreshToken(any())).willReturn("REFRESH_TOKEN"); ResponseCookie responseCookie = ResponseCookie.from("refreshToken", "REFRESH_TOKEN") .httpOnly(true) .secure(true) .sameSite("None") .path("/") .maxAge(3600000) .build(); given(memberService.createHttpOnlyCookie(any())).willReturn(responseCookie); // when ResultActions resultActions = mockMvc.perform(post("/admin/login") .contentType("application/json") .with(csrf()) .content(objectMapper.writeValueAsString(adminLoginRequest))); // then resultActions .andExpect(status().isOk()) .andExpect(result -> { String setCookieValue = result.getResponse().getHeader(HttpHeaders.SET_COOKIE); assertThat(setCookieValue).contains(responseCookie.getValue()); }) .andExpect(header().string("accessToken", "ACCESS_TOKEN")) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.admin").value(true)) .andExpect(jsonPath("$.writer").value(true)) .andDo(print()); }
따로 응답값의 헤더의 쿠키를 setCookieValue라는 변수병으로 저장하여, 예상되는 Cookie의 내용값을 비교하는 방법을 통해 테스트 코드를 작성하였다.
실제로 테스트 코드 성공을 시켰으며 테스트 코드를 작성하는 새로운 방법을 알아낸거같아서 기록할 겸 정리해 봤다.