inblog logo
|
sxias
    JUnit Test

    [JUnit Test] 3. MockMvc

    문정준's avatar
    문정준
    May 15, 2025
    [JUnit Test] 3. MockMvc
    Contents
    🧠 결론 요약

    🌐 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
    Contents
    🧠 결론 요약

    Sxias ㆍ a32176740@gmail.com

    RSS·Powered by Inblog