
최주호 강사님의 인프런 강좌 정리 및 실습한 기록, 이전 내용과 이어집니다.
@MockMvc 환경에서 특정 유저가 Account 를 DB 에 저장 한다.@Test 메서드 실행 전에, DB 에 AppUser 를 1건 집어넣는다./api/login 에서 Bearer ... 토큰을 가져오지 않고, 시큐리티 세션을 생성해서 로그인을 '한 것' 처럼 넘어가고, DB 를 타는 로직에만 집중한다.@WithUserDetails 어노테이션을 사용한다.해당 어노테이션은 @Test 가 실행될 때 마다 실행된다. 만약, N개 @Test 가 실행되면, 실행되는 것에는 순서가 없다.
다만, @Order(i) 를 사용해서 실행순서를 정할 수 있다.
스프링 시큐리티를 사용한다면, 인증 및 인가를 한 뒤 사용자가 토큰을 가지고 있어야 자원에 접근할 수 있다. 권한이 없다면 JWT 를 검증하는 단계에서 에러가 발생해서 더 이상 진행이 되지 않는다.
그럼 테스트 환경에서는 어떻게 해야할까?
강의에서는 다음과 같이 진행한다.

@WithUserDetails 를 이용해서 로그인이 '된 것' 처럼 진행한다.
즉, JwtAuthenticationFilter 를 통과하기 위해서 테스트 환경에서 Authentication 를 가지고 있는 시큐리티 세션을 생성한다.
그렇게 하기 위해서는, 옵션을 지정해줘야 한다. setupBefore = TestExecutionEvent.TEST_EXCECUTION)
default 로 설정된 setupBefore= TestExecutionEvent.TEST_METHOD) 는 @BeforeEach 처럼 다른 메서드 실행 이전 시점에 이미 실행이 되어버린다. 아직 DB 에 등록된 유저가 없는 시점에서 해당 유저의 인증 정보를 가져오려고 하니 오류가 발생한다.
/**
* jwt token 이 AuthenticationFilter 를 통과해서
* SecurityContextHolder 에 저장되며, Security Session 이 생성된다.
* 해당 세션은 ThreadLocal 에 저장되어 멀티-스레드 safe 하다.
* <p>
* {@link WithUserDetails} 은 DB 에서 <em>username=ssar</em> 을 조회해서 시큐리티 세션에 담는다. <br />
* {@code setupBefore=TEST_METHOD} 은 {@code setUp()} 메서드 실행전에 수행된다.
* {@code setUp()} 메서드가 실행되기 전에 ssar 을 찾으려고 하니 실패한다. <br />
* {@code setupBefore=TEST_EXECUTION} 은 {@code save_account_test()} 메서드 실행전에 수행된다.
*/
@Test
@DisplayName("save_account_test")
@WithUserDetails(value = "ssar", setupBefore = TestExecutionEvent.TEST_EXECUTION)
public void save_account_test() throws Exception {
/* given */
AccountSaveRequestDTO accountSaveRequestDTO = new AccountSaveRequestDTO();
accountSaveRequestDTO.setNumber(9999L);
accountSaveRequestDTO.setPassword(1234L);
String requestBody = objectMapper.writeValueAsString(accountSaveRequestDTO);
System.out.println(requestBody);
/* when */
ResultActions resultActions = mockMvc
.perform(post("/api/account/save").content(requestBody).contentType(MediaType.APPLICATION_JSON));
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println(responseBody);
/* then */
resultActions.andExpect(status().isCreated());
}


김영한님, 백기선님 강의에서도 마찬가지로 하나의 메서드에 여러가지 역할을 부여하는 것은 좋지 못하다. 관심사를 분리하는 것이 좋다.
어떤 환경을 구성하는 부분, 그리고 구성이 완료된 환경을 주입받아서 사용만 하는 부분 등 decoupling 을 고려해야한다.
Account 을 DB 에 저장하는 테스트도 이렇게 생각했다.
하지만 강의에서는 빠른 진도를 위해서도 있겠지만, 로그인하는 것 자체가 중요한 것이 아니고 Account 을 DB 에 저장하는 로직을 테스트하는 것이 목적이다.
로그인하고, 토큰 생성 후 JwtAuthenticationFilter 를 통과시키는 것 까지 단 2줄로 해결했다. 당연히 Spring Security 가 지원을 하기에 가능한 일이지만 아무런 생각을 하지 않았음을 반성한다.
import org.springframework.security.test.context.support.TestExecutionEvent;
import org.springframework.security.test.context.support.WithUserDetails;