자바 공부/스프링공부

Mockito 기반 단위 테스트에서 검증 방법 정리

ari0930 2025. 12. 31. 23:18

Mockito 기반 단위 테스트에서 검증 방법 정리

(단순 값 · 객체/리스트 · 예외 검증)

서비스 로직 테스트에서 가장 헷갈리는 부분은

“이 상황에서 무엇을 검증해야 하는가?” 입니다.

Mockito 기반 단위 테스트에서는

반환 값의 형태와 테스트 의도에 따라 검증 방식이 달라집니다.

이번 글에서는 실제 사용 중인 테스트 코드를 기준으로 다음 세 가지를 정리합니다.

  • 단순 값 비교
  • 객체 / 리스트 값 비교
  • 예외(에러 코드) 검증

테스트 환경

@ExtendWith(MockitoExtension.class)
publicclassUserHistoryServiceTest {

@Mock
    LoginHistoryMapper loginHistoryMapper;

@Mock
    PlayGameMapper playGameMapper;

@InjectMocks
    UserHistoryService userHistoryService;
}

  • Mapper는 Mock 처리
  • Service 로직만 단위 테스트
  • DB / Spring Context 미사용

1.단순 값 비교 (Primitive / Wrapper)

언제 사용하는가?

  • 서비스 메서드가 숫자, 문자열, boolean 같은 단순 값을 반환할 때
  • 계산 결과나 집계 결과를 검증할 때

예제: 오늘 DAU 조회

@Test
publicvoidgetTodayDau() {
// given
when(loginHistoryMapper.countDistinctUsersBetween(
            any(LocalDateTime.class),
            any(LocalDateTime.class)
    )).thenReturn(12L);

// when
Longresult= userHistoryService.getTodayDau();

// then
    assertThat(result).isEqualTo(12L);
}

핵심 포인트

  • assertThat(result).isEqualTo(12L)
  • 가장 단순하고 명확한 검증
  • equals 비교만 수행

파라미터까지 검증하고 싶을 때 (ArgumentCaptor)

ArgumentCaptor<LocalDateTime> startCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
ArgumentCaptor<LocalDateTime> endCaptor = ArgumentCaptor.forClass(LocalDateTime.class);

verify(loginHistoryMapper)
    .countDistinctUsersBetween(startCaptor.capture(), endCaptor.capture());

assertThat(startCaptor.getValue()).isEqualTo(today.atStartOfDay());
assertThat(endCaptor.getValue()).isEqualTo(today.plusDays(1).atStartOfDay());

“값이 맞다” + “올바른 파라미터로 호출되었다”를 함께 검증


2.객체 / 리스트 값 비교

언제 사용하는가?

  • DTO를 반환하는 서비스 로직
  • 여러 값을 조합하거나 가공한 결과를 검증할 때

예제: 기간별 DAU + 게임 참여 수 조회

@Test
publicvoidrangeUser() {
// given
when(loginHistoryMapper.countDailyDistinctUsersBetween(
            any(LocalDateTime.class),
            any(LocalDateTime.class)))
            .thenReturn(dau);

when(playGameMapper.selectDailyJoinCount(
            any(LocalDate.class),
            any(LocalDate.class)))
            .thenReturn(join);

// when
    List<DailyGameAndDauDto> result =
        userHistoryService.rangeUser(startDate, endDate);

// then
    assertThat(result)
        .extracting(
            DailyGameAndDauDto::getDate,
            DailyGameAndDauDto::getDau,
            DailyGameAndDauDto::getJoinCount
        )
        .containsExactly(
            tuple(LocalDate.of(2024,12,20),10L,10L),
            tuple(LocalDate.of(2024,12,21),12L,12L),
            tuple(LocalDate.of(2024,12,22),14L,14L)
        );
}


왜 extracting + tuple을 쓰는가?

  • DTO에 equals/hashCode가 없어도 테스트 가능
  • 어떤 필드를 검증하는지 명확
  • 순서까지 함께 검증 가능

isEqualTo(List) 와의 차이

assertThat(result).isEqualTo(expectedList);

  • DTO에 equals()가 없으면 실패
  • 내부 필드 비교가 불명확

=>가공 로직 검증에는 extracting이 더 안전


3.예외 검증 (에러 코드까지 확인)

언제 사용하는가?

  • 잘못된 입력이 들어왔을 때
  • 비즈니스 규칙 위반을 검증할 때
  • “실패가 정상 흐름”인 경우

예제 1: 시작 날짜가 종료 날짜보다 늦은 경우

@Test
void rangeUser_시작날짜가_종료날짜보다_늦으면_INVALID_DATE_RANGE() {
LocalDatestartDate= LocalDate.of(2024,12,22);
LocalDateendDate= LocalDate.of(2024,12,20);

    assertThatThrownBy(() ->
        userHistoryService.rangeUser(startDate, endDate)
    )
    .isInstanceOf(CustomException.class)
    .extracting("errorCode")
    .isEqualTo(ErrorCode.INVALID_DATE_RANGE);
}


예제 2: 종료 날짜가 미래인 경우

@Test
void rangeUser_종료날짜가_미래면_END_DATE_IN_FUTURE() {
LocalDatestartDate= LocalDate.now().minusDays(2);
LocalDateendDate= LocalDate.now().plusDays(1);

    assertThatThrownBy(() ->
        userHistoryService.rangeUser(startDate, endDate)
    )
    .isInstanceOf(CustomException.class)
    .extracting("errorCode")
    .isEqualTo(ErrorCode.END_DATE_IN_FUTURE);
}

 

반응형