300===Dev Framework/Spring Unit Test

Spring Mockito Test를 파헤쳐보자! 🧪

블로글러 2024. 11. 26. 13:31

📌 Spring Mockito란? 😋

안녕하세요! 오늘은 Spring Mockito에 대해 알아보겠습니다.
영화 촬영장에서 실제 건물이 아닌 가짜 세트장을 이용해 촬영하는 모습을 떠올려보세요.
촬영해야 할 장면에만 집중할 수 있도록 진짜 건물 대신 세트를 사용하죠.
Mockito 역시 이와 유사합니다. 실제 객체 대신 가짜(Mock) 객체를 만들어서
테스트 대상 코드만 격리하고, 테스트하고 싶은 핵심 기능을 명확하게 검증할 수 있도록 해주는 프레임워크입니다.


1. Mockito란? 🤔

Mockito는 자바 단위 테스트에서 자주 사용되는 Mocking 프레임워크입니다.

  • 🔹 가짜 객체(Mock Object) 생성: 실제 의존 객체가 아닌, Mock 객체를 만들어 테스트 대상 코드(서비스, 로직 등)와 분리합니다.
  • 🔹 독립적 테스트 가능: DB, 네트워크 등 외부 환경 없이도 테스트 시나리오를 완벽하게 통제할 수 있습니다.
  • 🔹 테스트 속도 향상: 실제 DB나 API가 필요 없으므로 속도가 빨라지고 CI/CD 파이프라인 수행 시간도 단축됩니다.

실생활 예시

  • 영화 세트장: 실제 거리나 건물 대신 세트장에서 촬영함으로써, 원하는 장면에만 집중하고 비용을 절감하죠.
  • 가상환경에서의 시뮬레이션: 예를 들어 항공기 시뮬레이터에서는 실제 비행기 없이 조종 훈련이 가능합니다.
    이와 유사하게 Mockito를 사용하면, 실제 의존성 없이도 핵심 로직을 검증할 수 있습니다.

2. 어떻게 동작하나요? 🎬

1) 기본 개념

Mockito는 Mock 객체를 만들어 메소드의 동작을 가짜로 정의할 수 있도록 해줍니다.
이를 통해, 테스트 시 원하는 시나리오에 맞춰 메소드를 호출했을 때 어떤 결과를 반환할지 미리 설정할 수 있고,
메소드가 실제로 어떻게 호출되었는지(호출 횟수, 인자 등) 검증할 수 있습니다.

// @Mock 애노테이션 사용
@Mock
private UserRepository userRepository;

// 또는 Mockito.mock() 사용
UserRepository userRepository = Mockito.mock(UserRepository.class);

// Mock 동작 정의
when(userRepository.findById("123"))
    .thenReturn(Optional.of(new User("123", "테스트 사용자")));

2) 실제 적용 예시

// 예: UserService를 테스트한다고 가정
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;  // 가짜 UserRepository

    @InjectMocks
    private UserService userService;        // 테스트 대상: 실제 로직

    @Test
    void 사용자_조회_테스트() {
        // given: Mock 객체 동작 설정
        String userId = "123";
        User mockUser = new User(userId, "테스트유저");
        when(userRepository.findById(userId))
            .thenReturn(Optional.of(mockUser));

        // when: 서비스 로직 실행
        User result = userService.getUser(userId);

        // then: 결과 검증 + Mock 객체가 제대로 호출되었는지 검증
        assertThat(result.getId()).isEqualTo(userId);
        verify(userRepository).findById(userId);
    }
}

🚀 동작 원리

  1. Mock 객체 생성: @Mock 혹은 Mockito.mock()으로 실제 객체를 대체할 Mock 객체를 만듭니다.
  2. Mock 동작 정의: when(...).thenReturn(...), when(...).thenThrow(...) 등으로 예측 가능한 결과를 설정합니다.
  3. 테스트 실행: 테스트 대상 코드를 호출하고, 정의된 Mock 동작대로 로직이 흘러갑니다.
  4. 검증(verify): verify()를 이용해 메소드가 어떻게 호출되었는지(호출 횟수, 인자, 순서 등) 확인합니다.

3. 주요 장점 🌟

  1. 독립적인 테스트
    • 외부 DB나 API 같은 실제 의존성 없이도, 테스트 대상 로직만 집중해 빠르고 안정적인 테스트를 수행할 수 있습니다.
  2. 빠른 실행 속도
    • DB 연결 등 I/O가 없어, 대규모 프로젝트에서도 테스트가 빠르게 진행됩니다.
    • CI/CD 환경에서 테스트 시간이 단축되어 빌드 파이프라인 최적화에 기여합니다.
  3. 예측 가능한 시나리오
    • Mock 객체가 미리 정의된 결과만 반환하기 때문에, 랜덤성이나 외부 환경의 영향을 최소화할 수 있습니다.
    • 버그 트래킹이 수월해지고, 테스트 신뢰도가 높아집니다.

4. 주의할 점 ⚠️

  1. 너무 많은 Mocking은 위험
    • 실제 동작과 Mock 동작이 달라 발생할 수 있는 괴리를 주의해야 합니다.
    • 가령 비즈니스 로직이나 쿼리 최적화 등은 실제 DB 통합 테스트로도 검증이 필요합니다.
  2. 통합 테스트와 병행
    • Mock을 이용한 단위 테스트만으로는 전체 시스템 동작을 100% 보장하기 어렵습니다.
    • 따라서 Mock 테스트 + 통합 테스트를 적절히 혼합해 다양한 관점에서 검증해야 합니다.
  3. Mock 객체의 한계
    • 복잡한 트랜잭션, 동시성 이슈 등은 Mock 객체로 완전 재현하기 어렵습니다.
    • 주요 시나리오 중심으로 Mock 동작을 설계하고, 나머지는 실제 환경에서 테스트하는 것이 좋습니다.

5. 실제 사용 예시 📱

1) 메소드 동작 설정

// 특정 값 반환
when(repository.findById(anyString()))
    .thenReturn(Optional.empty());

// 예외 발생
when(repository.save(any()))
    .thenThrow(new RuntimeException("테스트용 예외"));

// void 메소드 처리
doNothing().when(service).deleteUser(anyString());

2) 인자 매칭

// 정확한 값 매칭
when(repository.findById("123"))
    .thenReturn(Optional.of(new User("123", "테스트")));

// 임의의 값 매칭
when(repository.findById(anyString()))
    .thenReturn(Optional.of(new User("456", "아무값")));

// 조건부 매칭
when(repository.findById(argThat(id -> id.startsWith("user"))))
    .thenReturn(Optional.of(new User("user001", "조건부매칭")));

3) 검증 기능

// 호출 횟수 검증
verify(repository, times(1)).findById("123");
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findAll();

// 호출 순서 검증
InOrder inOrder = inOrder(repository, service);
inOrder.verify(repository).findById(anyString());
inOrder.verify(service).process(any());

6. 마치며 🎁

정리하자면, Mockito를 활용하면 테스트 로직외부 의존성을 깔끔하게 분리하여,
테스트 시나리오를 원하는 대로 설정하고 결과를 빠르게 검증할 수 있습니다.
물론 실제 환경과는 다른 결과가 나올 수 있으므로, 통합 테스트적절한 Mocking 범위 설정이 중요합니다.
하지만 적재적소에 제대로 활용한다면, 개발 생산성테스트 신뢰도를 크게 높일 수 있습니다!

"이 기술을 사용하면 외부 환경에 구애받지 않고도 핵심 비즈니스 로직을 빠르고 안정적으로 검증할 수 있어요!"

참고 자료 및 출처

Mock 객체를 활용해 테스트의 효율성과 신뢰도를 함께 높여보세요!

728x90