[JUnit Test] 3. MockMvc

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

🌐 1. 통합 테스트 환경 구성 어노테이션

@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)

🔹 @SpringBootTest(webEnvironment = ...)

전체 Spring 컨텍스트를 로드하고, 테스트할 웹 환경을 선택하는 설정.
옵션
설명
MOCK
내장 톰캣 없이 Mock 환경에서 테스트 (실제 HTTP 포트 사용 X) ✅ 가장 많이 씀
RANDOM_PORT
내장 톰캣을 사용하고 랜덤 포트에서 서버 실행
DEFINED_PORT
application.yml에서 지정한 포트로 실행
NONE
웹 환경 로딩 X, 순수한 자바 테스트처럼 사용

🔹 @AutoConfigureMockMvc

  • MockMvc 객체를 자동으로 등록 (IoC 컨테이너에 Bean으로 등록됨)
  • 직접 new MockMvc() 할 필요 없음
  • 단위 테스트와 달리 @WebMvcTest와 함께 쓰지 않음 → SpringBootTest로 전체 컨텍스트를 로드하기 때문

🔧 2. 테스트 코드 구성 요소

@Autowired private ObjectMapper om; @Autowired private MockMvc mvc;

🔹 ObjectMapper

  • Jackson 라이브러리 제공 객체
  • 자바 객체 ↔ JSON 문자열 변환 도구
  • ✅ 예: om.writeValueAsString(obj)String 타입 JSON 반환
UserRequest.JoinDTO dto = new UserRequest.JoinDTO(); dto.setUsername("haha"); ... String requestBody = om.writeValueAsString(dto);
💡 JavaScript에서는 JSON.stringify() 와 비슷한 역할
💡 실제 API 환경에서는 RestTemplate, WebClient 사용함 (Spring Boot)

🔹 MockMvc

  • 웹 계층을 서버 띄우지 않고 테스트하는 객체
  • 실제처럼 요청을 만들고, 응답을 받아 assert 가능

🚀 3. 테스트 요청 구성

ResultActions actions = mvc.perform( MockMvcRequestBuilders.post("/join") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) );

✅ 요청 구성 요약

  • POST /join : 요청 경로
  • .content(requestBody) : 바디에 JSON 데이터 전송
  • .contentType(MediaType.APPLICATION_JSON) : 헤더 설정 (Content-Type)

🖨️ 4. 응답 출력 (eye)

String responseBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(responseBody);
  • 응답 객체(MockHttpServletResponse)의 내용을 String으로 읽음
  • 터미널에 찍어보며 실제 결과 확인

✅ 예시 응답:

{ "status": 200, "msg": "성공", "body": { "id": 4, "username": "haha", "email": "haha@nate.com", "createdAt": "2025-05-13 12:40:50.879215" } }

✅ 5. then: 결과 검증

actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.msg").value("성공")); actions.andExpect(jsonPath("$.body.id").value(4)); actions.andExpect(jsonPath("$.body.username").value("haha")); actions.andExpect(jsonPath("$.body.email").value("haha@nate.com"));
  • jsonPath()는 JSON 응답에서 특정 경로의 값을 가져와 검증
  • value()로 기대값과 비교

❗ 6. 결과 값이 틀릴 때 로그 예시

예: body.id가 예상 3인데 실제 4일 경우
MockHttpServletRequest: HTTP Method = POST Request URI = /join Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"61"] Body = {"username":"haha","password":"1234","email":"haha@nate.com"} Session Attrs = {} Handler: Type = shop.mtcoding.blog.user.UserController Method = shop.mtcoding.blog.user.UserController#join(JoinDTO, Errors) Resolved Exception: Type = null MockHttpServletResponse: Status = 200 Content type = application/json;charset=UTF-8 Body = {"status":200,"msg":"성공","body":{"id":4,"username":"haha","email":"haha@nate.com","createdAt":"2025-05-13 12:41:38.091431"}} JSON path "$.body.id" Expected :3 Actual :4

🧠 로그 파악 포인트

항목
설명
MockHttpServletRequest
어떤 요청을 보냈는지 확인
Handler
어느 컨트롤러가 처리했는지
MockHttpServletResponse
어떤 응답을 보냈는지
JSON path
어떤 값이 기대와 달랐는지 (Expected vs Actual)

📌 결론: 통합 테스트란?

목적
실제 요청/응답 환경과 최대한 유사하게 API를 테스트
이점
1) 서버를 띄우지 않음2) 빠름3) DB까지 연동되므로 실전과 유사
사용 도구
@SpringBootTest, @AutoConfigureMockMvc, MockMvc, ObjectMapper
주의
테스트 데이터를 조작하므로 테스트 후 롤백하거나 분리된 DB 사용 권장

🧩 번외: 만약 H2가 아니라 MySQL로 테스트했다면 벌어질 수 있는 현실적인 테스트 문제

테스트 코드를 짜다 보면 조회 테스트를 하고 싶은데, 더미 데이터가 없어서 막히는 경우가 많다.
→ 그래서 insert 먼저 하고, 그 데이터를 바탕으로 조회 테스트하고 싶어짐

✅ 하지만 이건 테스트 원칙을 어기는 것

❌ 잘못된 흐름

  1. 테스트1: insert
  1. 테스트2: select
    1. → 테스트 1의 결과에 의존
하지만 테스트는 기본적으로 독립적이어야 함.
(순서에 의존 X, 이전 테스트 결과 X)

🧨 왜 어려운가? 테스트 롤백 + DB 상태 유지 문제

  • 대부분 테스트에는 @Transactional이 붙어서 테스트 종료 시 자동 롤백
  • 그런데 다음 테스트에서 select 하려면 insert가 필요 → 롤백되면 select가 실패
  • 반대로 롤백 안 하고 @Rollback(false) 설정하면
    • → DB가 오염되거나, 중복 데이터로 인해 테스트가 다시 실패하는 일이 발생

🙅‍♂️ 이게 바로 실무에서 테스트 어렵다는 이유

  • 특히 H2 안 쓰고 MySQL 쓰는 경우, 로컬 DB 상태에 따라 테스트가 불안정
  • 그래서 회사에선 더미 데이터 관리 전략이 반드시 필요함

💡 실무 해결 전략

방법
설명
@Sql 어노테이션
테스트 전 미리 insert SQL 실행
@BeforeEach
자바 코드로 매 테스트마다 더미 객체 생성
@Rollback(false)
특정 테스트에선 롤백하지 않음 (주의 필요)
테스트 전용 DB
테스트 전용 MySQL DB를 따로 분리해서 운영
테스트 전용 서비스 계층
insert 후 select 테스트 흐름 분리 가능

🔄 예시: 테스트 중 더미 데이터 수동 삽입

@BeforeEach public void setUp() { User user = new User("testuser", "1234", "test@aaa.com"); userRepository.save(user); }
✅ 이렇게 하면 모든 테스트에서 testuser는 존재하므로 조회/중복 검증이 가능

🧠 결론 요약

  • 중복 체크는 insert 지점에서 한 번 더 검증하는 게 설계적으로 맞다
  • 테스트는 독립적으로! 서로 의존하거나 순서에 따라 다르면 유지보수 불가능
  • 테스트가 어려운 이유는 DB 상태가 계속 바뀌기 때문 → @Sql, @BeforeEach, 전용 DB 활용이 해답
  • 실무에서는 MockMvc, ObjectMapper, @SpringBootTest를 활용한 통합 테스트가 주력이며, 이 과정에서 데이터 세팅이 가장 중요함
 
Share article

sxias