테스트 가이드

JUnit5 + MockMvc 기반 단위/통합 테스트 작성 규칙

마지막 수정: 2026-05

테스트 가이드

기본 원칙

  • 테스트 없는 코드는 완성된 코드가 아니다
  • 단위 테스트(Unit Test)와 통합 테스트(Integration Test) 구분
  • 테스트 코드도 프로덕션 코드와 동일한 품질 기준 적용
  • Given-When-Then 패턴 사용
  • 테스트 Coverage 목표: 80% 이상

테스트 클래스 어노테이션

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("unitTest")
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    // ...
}
어노테이션설명
@SpringBootTest전체 Spring Context 로드
@AutoConfigureMockMvcMockMvc 자동 설정
@ActiveProfiles("unitTest")unitTest 프로파일 활성화

MockMvc + ObjectMapper 사용

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("unitTest")
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    @DisplayName("사용자 등록 성공")
    void regist_success() throws Exception {
        // Given
        UserVo vo = new UserVo();
        vo.setUserId("user01");
        vo.setUserNm("홍길동");
        vo.setUserEmail("hong@example.com");

        // When & Then
        mockMvc.perform(post("/api/user/regist")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(vo)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
    }

    @Test
    @DisplayName("사용자 목록 조회 성공")
    void getList_success() throws Exception {
        // Given
        UserVo vo = new UserVo();
        vo.setRowPerPage(10);
        vo.setOffset(0);

        // When & Then
        mockMvc.perform(post("/api/user/getList")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(vo)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data").isArray());
    }
}

테스트 메서드 네이밍

형식: 메서드명_상황_기대결과

@Test
@DisplayName("사용자 등록 성공 시 200 반환")
void regist_success_returns200() throws Exception { ... }

@Test
@DisplayName("필수값 누락 시 400 반환")
void regist_missingRequiredField_returns400() throws Exception { ... }

@Test
@DisplayName("아이디 중복 시 에러 응답")
void regist_duplicateUserId_returnsError() throws Exception { ... }

단위 테스트 (Service)

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserMapper userMapper;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("사용자 조회 성공")
    void get_existingUser_returnsUser() {
        // Given
        UserVo vo = new UserVo();
        vo.setUserSn(1L);

        UserVo mockUser = new UserVo();
        mockUser.setUserSn(1L);
        mockUser.setUserNm("홍길동");
        given(userMapper.get(vo)).willReturn(mockUser);

        // When
        UserVo result = userService.get(vo);

        // Then
        assertThat(result).isNotNull();
        assertThat(result.getUserNm()).isEqualTo("홍길동");
    }

    @Test
    @DisplayName("존재하지 않는 사용자 조회 시 예외 발생")
    void get_nonExistingUser_throwsException() {
        // Given
        UserVo vo = new UserVo();
        vo.setUserSn(999L);
        given(userMapper.get(vo)).willReturn(null);

        // When & Then
        assertThatThrownBy(() -> userService.get(vo))
                .isInstanceOf(BusinessException.class);
    }
}

JsonPath 표현식 예시

// 최상위 필드 검증
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("등록되었습니다."))

// 배열 검증
.andExpect(jsonPath("$.data").isArray())
.andExpect(jsonPath("$.data").isEmpty())
.andExpect(jsonPath("$.data", hasSize(3)))

// 중첩 필드 검증
.andExpect(jsonPath("$.data.userId").value("user01"))
.andExpect(jsonPath("$.data.userNm").value("홍길동"))

// 배열 요소 검증
.andExpect(jsonPath("$.data[0].userId").value("user01"))
.andExpect(jsonPath("$.data[0].userNm").value("홍길동"))

Hamcrest Matchers 예시

import static org.hamcrest.Matchers.*;

// 크기 검증
.andExpect(jsonPath("$.data", hasSize(5)))

// null 검증
.andExpect(jsonPath("$.data").value(IsNull.notNullValue()))

// 포함 검증
.andExpect(jsonPath("$.data[*].userId", hasItem("user01")))

// 범위 검증
.andExpect(jsonPath("$.totalCount").value(greaterThan(0)))

테스트 설정 파일

# src/test/resources/application-unitTest.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb;MODE=Oracle
    driver-class-name: org.h2.Driver
  sql:
    init:
      mode: always
      schema-locations: classpath:test-schema.sql

AssertJ 사용 (권장)

// assertThat (AssertJ) — 가독성 높음
assertThat(result).isNotNull();
assertThat(result).hasSize(3);
assertThat(result.getUserNm()).isEqualTo("홍길동");
assertThatThrownBy(() -> service.get(vo))
        .isInstanceOf(BusinessException.class)
        .hasMessageContaining("찾을 수 없습니다");

테스트 작성 체크리스트

  • [ ] @SpringBootTest, @AutoConfigureMockMvc, @ActiveProfiles("unitTest") 사용
  • [ ] MockMvc + ObjectMapper 주입
  • [ ] 테스트 메서드명: 메서드명_상황_기대결과 형식
  • [ ] Given-When-Then 구조 준수
  • [ ] @DisplayName으로 테스트 목적 명시
  • [ ] JsonPath로 응답 검증
  • [ ] 경계 조건(null, 빈 값, 최대값) 테스트 포함
  • [ ] Coverage 목표: 80% 이상