소프트웨어 테스트는 검증하려는 범위와 목적에 따라 여러 종류로 나뉩니다. 테스트 피라미드는 안정적이고 효율적인 테스트 전략을 수립하기 위한 모델입니다.

단위 테스트 (Unit Test) - 피라미드의 가장 넓은 기반:
통합 테스트 (Integration Test) - 피라미드의 중간:
@SpringBootTest가 대표적)E2E 테스트 (End-to-End Test) - 피라미드의 최상단:
문제점: UserService의 createUser 메서드를 단위 테스트하고 싶다고 가정해봅시다. 이 메서드는 내부적으로 UserRepository에 의존하여 DB에 접근합니다. 만약 실제 UserRepository를 사용하면, 이는 DB라는 외부 의존성을 포함하게 되어 더 이상 순수한 단위 테스트가 아니게 됩니다. (DB 상태에 따라 테스트 결과가 달라질 수 있음)
해결책 (Mocking): Mock(가짜) 객체를 사용하여 실제 의존성을 대체하는 기술입니다. Mock 객체는 실제 로직을 수행하지 않고, 우리가 "이렇게 동작해라"라고 미리 정의한 행동만 수행하는 껍데기 객체입니다.
Mockito는 Mock 객체를 쉽게 생성하고, 그 행동을 정의할 수 있도록 도와주는 라이브러리입니다. Spring Boot 테스트 스타터에 기본적으로 포함되어 있습니다.
주요 Mockito 어노테이션 및 메서드:
@Mock: 가짜(Mock) 객체를 생성합니다. 이 객체는 비어있는 껍데기입니다.@InjectMocks: 테스트 대상이 되는 객체를 생성하고, @Mock으로 생성된 가짜 객체들을 자동으로 주입해줍니다.when(mockObject.method(argument)).thenReturn(returnValue): Mock 객체의 특정 메서드가 특정 인자로 호출될 때, 어떤 값을 반환할지를 정의(Stubbing)합니다.verify(mockObject).method(argument): 테스트가 끝난 후, Mock 객체의 특정 메서드가 예상대로 호출되었는지를 검증합니다.@ExtendWith(MockitoExtension.class))@SpringBootTest 대신, Spring 컨테이너의 도움 없이 순수 JUnit과 Mockito만으로 서비스 계층을 테스트하는 방법입니다.// Spring 컨테이너를 로드하지 않고, Mockito 기능을 활성화
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock // 가짜 UserRepository 생성
private UserRepository userRepository;
@InjectMocks // 테스트 대상인 UserService에 위의 가짜 userRepository를 주입
private UserService userService;
@Test
@DisplayName("회원가입 성공 테스트")
void signUp_Success() {
// given - 테스트 준비
SignUpRequest request = new SignUpRequest("test@example.com", "password123");
User mockUser = new User(1L, "test@example.com", "encodedPassword");
// userRepository.findByEmail()이 호출되면, 비어있는 Optional을 반환하도록 정의 (중복 이메일 없음)
when(userRepository.findByEmail(request.getEmail())).thenReturn(Optional.empty());
// userRepository.save()가 어떤 User 객체로든 호출되면, 미리 만들어둔 mockUser를 반환하도록 정의
when(userRepository.save(any(User.class))).thenReturn(mockUser);
// when - 테스트 실행
User result = userService.signUp(request);
// then - 결과 검증
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getEmail()).isEqualTo("test@example.com");
// userRepository의 save 메서드가 정확히 1번 호출되었는지 검증
verify(userRepository, times(1)).save(any(User.class));
}
@Test
@DisplayName("회원가입 실패 - 이메일 중복")
void signUp_Fail_EmailDuplicated() {
// given
SignUpRequest request = new SignUpRequest("test@example.com", "password123");
User existingUser = new User(1L, "test@example.com", "password");
// userRepository.findByEmail()이 호출되면, 이미 존재하는 사용자를 반환하도록 정의
when(userRepository.findByEmail(request.getEmail())).thenReturn(Optional.of(existingUser));
// when & then
// signUp 메서드가 BusinessException을 던지는지 검증
assertThrows(BusinessException.class, () -> {
userService.signUp(request);
});
// userRepository의 save 메서드가 절대 호출되지 않았는지 검증
verify(userRepository, never()).save(any(User.class));
}
}
@Mock으로 가짜 객체를 만들고, when().thenReturn()으로 행동을 정의(Stubbing)하며, verify()로 호출 여부를 검증하는 방식으로 단위 테스트를 작성할 수 있게 해주는 강력한 프레임워크입니다.@ExtendWith(MockitoExtension.class)를 사용하면, 서비스 계층과 같이 순수 Java 로직에 가까운 부분을 매우 가볍고 빠르게 테스트할 수 있습니다.