[JUnit Test] 4. MockMvc + JWT

문정준's avatar
May 15, 2025
[JUnit Test] 4. MockMvc + JWT

📌 목표

  • JWT 인증이 필요한 API (POST /s/api/board)를 테스트
  • 로그인 토큰이 필요하기 때문에 테스트에 accessToken을 설정해야 함
  • 매번 토큰 생성 코드를 작성하는 불편함 → @BeforeEach로 공통 초기화 처리

🧪 1. 변경 전 테스트 코드 흐름

👇 테스트 내에서 직접 accessToken을 생성

@Test public void save_test() throws Exception { // given (가짜 데이터) BoardRequest.SaveDTO reqDTO = new BoardRequest.SaveDTO(); reqDTO.setTitle("제목1"); reqDTO.setContent("내용1"); reqDTO.setPublic(true); String requestBody = om.writeValueAsString(reqDTO); // when (테스트 실행) User ssar = User.builder().id(1).username("ssar").build(); // 👈 직접 생성 accessToken = JwtUtil.create(ssar); // 👈 직접 발급 ResultActions actions = mvc.perform( MockMvcRequestBuilders .post("/s/api/board") .header("Authorization", "Bearer " + accessToken) .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제목1")); }

🔍 문제점

  • 매 테스트마다 User, accessToken 생성 코드가 반복됨
  • 테스트 메서드가 지저분하고 길어짐
  • 테스트 목적이 흐려짐 (인증 관련 코드 vs 테스트 본래 목적이 섞임)

🔄 2. 변경 후 (BeforeEach로 리팩토링)

✅ 공통 토큰 생성 로직을 @BeforeEach에 작성

@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class BoardControllerTest { @Autowired private MockMvc mvc; @Autowired private ObjectMapper om; private String accessToken; // 테스트마다 재사용할 변수 @BeforeEach public void setup() { // ✅ 테스트 시작 전 실행 System.out.println("setup"); User ssar = User.builder().id(1).username("ssar").build(); // 가짜 유저 accessToken = JwtUtil.create(ssar); // JWT 발급 } @AfterEach public void tearDown() { // ✅ 테스트 후 정리 작업 System.out.println("tear down"); } @Test public void save_test() throws Exception { // given BoardRequest.SaveDTO reqDTO = new BoardRequest.SaveDTO(); reqDTO.setTitle("제목1"); reqDTO.setContent("내용1"); reqDTO.setPublic(true); String requestBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( MockMvcRequestBuilders .post("/s/api/board") .header("Authorization", "Bearer " + accessToken) // 👈 재사용 .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody); // then actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200)); actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공")); actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.title").value("제목1")); } }

🔍 BeforeEach / AfterEach 설명

어노테이션
설명
@BeforeEach
각 테스트 메서드 실행 전 호출됨공통으로 필요한 준비 작업 (예: 토큰 생성, DB 초기화 등)을 작성
@AfterEach
각 테스트 메서드 실행 후 호출됨자원 해제, 로그 출력, 트랜잭션 롤백 등 정리 작업 수행

✅ 리팩토링 효과

  • 테스트 코드 내 중복 제거
  • 테스트 목적이 명확하게 분리됨
    • @BeforeEach: 인증 세팅
    • @Test: 실제 기능 테스트
  • 관리/유지보수 용이

🧠 보너스 팁

  • accessToken = JwtUtil.create(...)는 대부분의 테스트에서 공통되므로, 이 방식은 대부분의 JWT 기반 API 테스트에 적용 가능
  • 인증 테스트 시 토큰 없이 요청하여 401 Unauthorized 반환 확인도 가능
  • @Transactional이 붙은 테스트는 실행 후 자동 롤백되므로, 테스트 간 데이터 충돌이 적음

🧩 [번외] 날짜 필드 createdAt 정규식 검증

 

✅ 왜 정규식을 사용하는가?

createdAt 필드는 보통 시스템이 자동 생성하는 날짜/시간 값입니다.
이 값은 테스트 실행 시마다 매번 다르게 생성되므로, 정확한 문자열 값을 value("2025-05-13 15:46:14.216746")처럼 비교할 수 없습니다.
→ 따라서 "형식(패턴)"만 맞는지 확인하기 위해 정규식(Regex) 검증을 사용합니다.

🔤 코드 예시

actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.createdAt") .value(Matchers.matchesPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d+$")));

🔍 코드 해석

📍 $.body.createdAt

  • JSON 응답 중 body 객체 안에 있는 createdAt 필드를 의미

📍 Matchers.matchesPattern(...)

  • 해당 값이 정규식 패턴과 일치하는지 확인

📍 정규식 설명

^ // 문자열의 시작 \d{4} // 연도: 숫자 4자리 (예: 2025) - // 하이픈 \d{2} // 월: 숫자 2자리 (예: 05) - // 하이픈 \d{2} // 일: 숫자 2자리 (예: 13) \s // 공백 \d{2} // 시: 숫자 2자리 (예: 15) : // 콜론 \d{2} // 분: 숫자 2자리 (예: 46) : // 콜론 \d{2} // 초: 숫자 2자리 (예: 14) \. // 점 (소수점 앞뒤 구분) \d+ // 밀리초 또는 마이크로초 (숫자 여러 자리) $ // 문자열의 끝

✅ 예시로 통과되는 날짜 문자열

2025-05-13 15:46:14.216746

💡 정규식을 쓰는 이유 요약

이유
설명
✔ 실행마다 값이 달라짐
createdAt은 테스트할 때마다 시간이 달라지므로 고정값 비교가 불가능
✔ 형식만 검증하면 충분
날짜가 예상된 **형식(패턴)**에 맞는지만 확인하면 신뢰할 수 있음
✔ 유지보수 용이
테스트 깨질 확률 ↓, 불필요한 테스트 수정 방지 ↑

🧪 응용 예시

  • 이메일 형식 검증: ^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,}$
  • 핸드폰 번호 검증: ^010-\\d{4}-\\d{4}$
  • UUID 형식: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
Share article

sxias