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);
}
}
🚀 동작 원리
- Mock 객체 생성:
@Mock
혹은Mockito.mock()
으로 실제 객체를 대체할 Mock 객체를 만듭니다. - Mock 동작 정의:
when(...).thenReturn(...)
,when(...).thenThrow(...)
등으로 예측 가능한 결과를 설정합니다. - 테스트 실행: 테스트 대상 코드를 호출하고, 정의된 Mock 동작대로 로직이 흘러갑니다.
- 검증(verify):
verify()
를 이용해 메소드가 어떻게 호출되었는지(호출 횟수, 인자, 순서 등) 확인합니다.
3. 주요 장점 🌟
- 독립적인 테스트
- 외부 DB나 API 같은 실제 의존성 없이도, 테스트 대상 로직만 집중해 빠르고 안정적인 테스트를 수행할 수 있습니다.
- 빠른 실행 속도
- DB 연결 등 I/O가 없어, 대규모 프로젝트에서도 테스트가 빠르게 진행됩니다.
- CI/CD 환경에서 테스트 시간이 단축되어 빌드 파이프라인 최적화에 기여합니다.
- 예측 가능한 시나리오
- Mock 객체가 미리 정의된 결과만 반환하기 때문에, 랜덤성이나 외부 환경의 영향을 최소화할 수 있습니다.
- 버그 트래킹이 수월해지고, 테스트 신뢰도가 높아집니다.
4. 주의할 점 ⚠️
- 너무 많은 Mocking은 위험
- 실제 동작과 Mock 동작이 달라 발생할 수 있는 괴리를 주의해야 합니다.
- 가령 비즈니스 로직이나 쿼리 최적화 등은 실제 DB 통합 테스트로도 검증이 필요합니다.
- 통합 테스트와 병행
- Mock을 이용한 단위 테스트만으로는 전체 시스템 동작을 100% 보장하기 어렵습니다.
- 따라서 Mock 테스트 + 통합 테스트를 적절히 혼합해 다양한 관점에서 검증해야 합니다.
- 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