Controller - Service - Repository 테스트나 해봤지 Filter의 테스트 코드는 처음 해봤다.
정확히 내가 하고자 하는 것은, Access Token 유효성을 검사하는 JwtFiler와 JwtFilter에서 발생한 예외를 처리하는 JwtExceptionFilter를 검사하는 것!
알아볼 내용을 대충 정리하면 다음과 같고,
JwtFilter
- 가짜 Request, 가짜 헤더 만들기
JwtExceptionFilter
- 앞 필터의 예외 상황 mocking하기
결론을 정리하면 다음과 같다.
그냥 다른 레이어의 유닛 테스트와 다를 것 없음 ㅋㅋㅋㅋ
우선 전체 코드
@ExtendWith(MockitoExtension.class)
public class JwtFilterTest {
@Mock
private TokenService tokenService;
@InjectMocks
private JwtFilter jwtFilter;
@Mock
private FilterChain filterChain;
@Test
@DisplayName("유효한 액세스 토큰 도착 시, 인증 정보 설정")
public void doFilterInternal_WithValidToken_ShouldSetAuthentication() throws ServletException, IOException {
// Given
String validToken = "ValidToken";
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader(JwtFilter.AUTHORIZATION_HEADER, JwtFilter.BEARER_PREFIX + validToken);
Claims claims = mock(Claims.class);
when(claims.get("aud")).thenReturn("1");
when(claims.getSubject()).thenReturn("username");
AccessToken accessToken = mock(AccessToken.class);
when(accessToken.getData()).thenReturn(claims);
when(tokenService.convertAccessToken(anyString())).thenReturn(accessToken);
// When
jwtFilter.doFilterInternal(request, response, filterChain);
// Then
verify(tokenService, times(1)).convertAccessToken(validToken);
verify(tokenService, times(1)).setAuthentication(1L, "username");
verify(filterChain, times(1)).doFilter(request, response);
}
// 생략...
}
생략했지만 위 세 가지 시나리오로 테스트 코드를 작성했다.
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader(JwtFilter.AUTHORIZATION_HEADER, JwtFilter.BEARER_PREFIX + validToken);
MockHttpServletRequest는 HttpServletRequest의 인터페이스를 구현하고 있어서,
1) 실제 서버/클라이언트 없이도 가짜 요청을 만들 수 있고,
2) addHeader(), setMethod(), setRequestURI() 등의 메소드를 사용할 수 있다.
나의 경우 헤더에 토큰을 넣는 용도로 사용했다.
cf) HttpServletRequest
doGet(), doPost()에서 쓰던 그거..@ExtendWith(MockitoExtension.class)
class JwtExceptionFilterTest {
@InjectMocks
private JwtExceptionFilter jwtExceptionFilter;
@Mock
private FilterChain filterChain;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private PrintWriter printWriter;
@Test
@DisplayName("ExpiredJwtException 발생 시, 적절한 예외처리 객체 반환")
void doFilterInternal_WithExpiredJwtException() throws IOException, ServletException {
// Given
doThrow(new ExpiredJwtException(null, null, "Token expired"))
.when(filterChain).doFilter(request, response);
when(response.getWriter()).thenReturn(printWriter);
// When
jwtExceptionFilter.doFilterInternal(request, response, filterChain);
// Then
verify(response).setStatus(HttpStatus.UNAUTHORIZED.value());
verify(response).setContentType("application/json; charset=UTF-8");
ErrorResponseDto expectedErrorResponse = new ErrorResponseDto(ACCESS_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED);
verify(printWriter).write(new ObjectMapper().writeValueAsString(expectedErrorResponse));
}
// 생략...
}
코드는 생략했지만 이번에도
네 가지 시나리오로 작성했다.
doThrow(new ExpiredJwtException(null, null, "Token expired"))
.when(filterChain).doFilter(request, response);
이 부분을 잘 보고, 그냥 filterChain.doFilter에서 원하는 예외가 난 상황을 만들어주면 된다.
Filter 코드를 짜면서 계속 든 생각은 'Request도 모킹이 되네?', 'FilterChain도 mocking이 되네?' 하는 것이었는데,
막상 정리하면서 다시 보니 왜 쟤네들은 모킹할 수 없다고 생각했는지 모르겠다는 생각이 든다.
어차피 다 그냥 stub일 뿐인데...