목차
1. Mock 객체
- 가짜 객체(Fake Object)
- 불필요한 의존을 제거하고 단위에 집중된 테스트 가능
- Mockito: Mock 객체를 만들어주는 도구
1) Stub vs Mock vs Spy
| 항목 | Stub | Mock | Spy |
|---|---|---|---|
| 목적 | 고정된 응답 반환 | 호출 여부/순서/횟수 검증, 행동 설정 가능 | 실제 객체를 감싸서 일부 메서드만 mocking |
| 구현 | 테스트 대상 외부 동작을 하드코딩하여 제공 | 미리 동작 정의하거나 호출 결과를 검증 | 실제 객체와 결합되어 실제 동작도 가능 |
| 사용 시점 | 간단한 조건/결과가 필요한 테스트 | 외부 시스템이 복잡하거나 테스트 검증이 필요한 경우 | 일부만 대체하고 나머지는 실제 동작해야 할 때 |
Stub 예시
// Stub 예시
EmailService stubEmailService = new EmailService() {
@Override
public boolean sendEmail(String to, String message) {
return true; // 항상 성공
}
};
Mock 예시 (Mockito 사용)
// Mock 예시
EmailService mockEmailService = mock(EmailService.class);
when(mockEmailService.sendEmail(anyString(), anyString())).thenReturn(true);
verify(mockEmailService, times(1)).sendEmail("user@example.com", "가입을 환영합니다");
Spy 예시 (일부만 mocking)
// Spy 예시
EmailService realService = new EmailService();
EmailService spyService = spy(realService);
doReturn(true).when(spyService).sendEmail(anyString(), anyString());
2) 실무 팁
- Mock 객체가 너무 많아진다면, 코드 구조를 재검토 필요 (SRP 위반 가능성)
- Mock 정의는 Fixture 또는 설정 클래스로 분리하면 재사용성이 높아짐
- 통합 테스트에서는 실제 의존 객체를 사용하고, 단위 테스트나 슬라이스 테스트에서만 Mock을 활용하는 것이 이상적
3) 중요 개념 정리
| 항목 | 설명 |
|---|---|
| Mock이란? | 실제 객체처럼 동작하지만 테스트에 맞게 조작 가능한 가짜 객체 |
| 목적 | 외부 의존 제거, 테스트 속도 향상, 예외 상황 시뮬레이션, 단위 분리 검증 |
| Stub | 고정된 응답만을 제공하는 단순 객체 |
| Spy | 실제 객체에 대한 래퍼, 일부 동작만 mocking |
| Mockito | 가장 널리 쓰이는 Java Mocking 프레임워크, Spring과도 연동됨 |
2. Mockito 기본 기능
: Mocking Framework, 테스트 대상 클래스의 의존 객체를 가짜(Mock)로 대체하여 테스트의 정확성, 독립성, 실행 속도 보장
1) 디렉토리 구조
src
├── main
│ └── com.example.service
└── test
├── com.example.service
└── com.example.fixture
2) Mock 객체 생성 방법
Mockito.mock() 메서드
import static org.mockito.Mockito.mock;
@Test
void testWithMockMethod() {
UserRepository userRepository = mock(UserRepository.class);
// userRepository를 사용한 테스트 코드
}
@Mock 애너테이션 (권장)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Test
void testWithMockAnnotation() {
// userRepository는 이미 Mock 객체로 초기화되어 있음
}
}
@MockBean 애너테이션
@SpringBootTest
class UserServiceIntegrationTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService; // 자동으로 mockRepository가 주입됨
}
3) Mock 객체 주입 기법
생성자 주입
@Test
void testWithConstructorInjection() {
UserRepository mockRepository = mock(UserRepository.class);
UserService userService = new UserService(mockRepository);
// userService를 사용한 테스트
}
@InjectMocks 애너테이션 (권장)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testWithInjectMocks() {
// userService는 이미 mockRepository가 주입된 상태
}
}
복잡한 의존성 구조 처리
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private ProductRepository productRepository;
@Mock private UserRepository userRepository;
@Mock private PaymentGateway paymentGateway;
@InjectMocks private ProductService productService;
@InjectMocks private UserService userService;
@InjectMocks private OrderService orderService;
@Test
void testComplexDependencies() {
// 복잡한 서비스-레포지토리 구조에서도 자동 주입됨
}
}
@MockBean과 Spring Context 주입
@SpringBootTest
class UserServiceIntegrationTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService; // 컨텍스트에서 mockRepository가 주입됨
}
4) 요약
| API | 설명 |
|---|---|
| mock() | 정적 메서드로 Mock 객체를 수동 생성 |
| @Mock | 선언적으로 Mock 객체 자동 생성 |
| @MockBean | Spring 컨텍스트에 Mock 객체 등록 (통합 테스트용) |
| @InjectMocks | 테스트 대상 클래스에 Mock 의존성을 자동 주입 |
| when() | 특정 입력에 대한 Mock 동작 설정 |
| verify() | Mock 메서드 호출 여부/횟수/순서 검증 |
5) 실무 팁
- 단위 테스트에는 @Mock + @InjectMocks 권장
- 통합 테스트에는 @MockBean + @Autowired 권장
3. Mock 객체 동작
: 기본적으로 모든 메서드는 null, false, 0, 빈 컬렉션 등을 반환하므로 명시적으로 원하는 동작 설정 필요
when().thenReturn() 구문
// mockObject의 someMethod가 주어진 args로 호출되면, value를 반환
when(mockObject.methodCall(arguments)).thenReturn(expectedValue);
예제
@Mock
private UserRepository userRepository;
@Test
void should_return_user_when_email_exists() {
User user = new User("test@example.com", "홍길동");
when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(user));
Optional result = userRepository.findByEmail("test@example.com");
assertTrue(result.isPresent());
assertEquals("홍길동", result.get().getName());
}
verify() 메서드
verify(mockObject).methodCall(arguments);
예제
@Test
void should_send_email_once() {
when(emailService.sendEmail(anyString(), anyString())).thenReturn(true);
userService.register("user@example.com");
verify(emailService, times(1)).sendEmail("user@example.com", "가입을 환영합니다");
}
ArgumentMatchers 활용
when(emailService.sendEmail(anyString(), eq("가입을 환영합니다"))).thenReturn(true);
verify(emailService).sendEmail(anyString(), eq("가입을 환영합니다"));
추가적인 Mock 동작 기법
doThrow() 예외 발생 시뮬레이션
doThrow(new RuntimeException("삭제 실패")).when(repository).deleteById(1L);
assertThrows(RuntimeException.class, () -> repository.deleteById(1L));
doNothing()
doNothing().when(repository).deleteById(1L);
repository.deleteById(1L);
verify(repository).deleteById(1L);
doAnswer() 커스텀 응답 설정
doAnswer(invocation -> {
String name = invocation.getArgument(0);
return new User(name + "@mock.com");
}).when(userService).createUser(anyString());
User user = userService.createUser("taekho");
assertEquals("taekho@mock.com", user.getEmail());
실무 팁
- `when().thenReturn()` → Mock 객체 상태(결과) 설정
- `verify()` → Mock 객체 행위(호출) 검증
- ArgumentMatchers 필수, 혼합 사용 주의
- `verify(mock, never()).method()`로 호출되지 않음 검증 가능
- `doThrow()`, `doAnswer()`로 예외/커스텀 시나리오 테스트 가능
728x90
'Software Engineering > Test & Quality' 카테고리의 다른 글
| [TDD] 6. Controller 계층 테스트 (0) | 2025.08.20 |
|---|---|
| [TDD] 5. Service 계층 테스트 (4) | 2025.08.20 |
| [TDD] 3. Spring Data JPA: 데이터 액세스 계층 테스트 (1) | 2025.08.18 |
| [TDD] 2. JUnit 5 (2) | 2025.08.18 |
| 상황에 맞는 예외 정리 (4) | 2025.08.14 |