지난번에 TestNG 에 대해서 공부하다보니, 테스팅 자체에 대한 개념이 부족하다는걸 느꼈다.
그래서 피하고 피해왔던.. 차후 멋진 엔지니어로서 예쁘고 멋진 테스트 코드 작성과 튜닝을 할 수 있는 사람이 되기위해..ㅜ
몰랐던 개념들에 대해 하나하나 짚어보고자 한다.
뭐부터 공부할까 고민하다가 걍 눈에 보이는것들 먼저 짚어보았다.

#1. Mock
Mock(모의 객체) 은 말 그대로 “진짜 객체처럼 동작하지만 실제 로직은 수행하지 않는 가짜 객체”
테스트 대상 코드가 외부 의존성(데이터베이스, 파일, API 등) 없이 독립적으로 검증될 수 있도록 도와줌.
class UserService {
private EmailService emailService;
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void registerUser(String email) {
// 1. 사용자 등록 로직
// 2. 이메일 발송
emailService.sendWelcomeEmail(email);
}
}
이제 UserService를 테스트하고 싶은데,
여기서 진짜 EmailService를 호출하면 이메일이 실제로 전송될 수도 있다.
➡️ 테스트 환경에서는 이런 외부 의존(이메일, DB, 서버 호출 등)을 피해야 한다.
그래서 Mock 객체를 만들어 이렇게 “가짜 EmailService”로 대체한다.
EmailService mockEmailService = mock(EmailService.class);
UserService userService = new UserService(mockEmailService);
이렇게 하면 실제 이메일은 안 보내고,
테스트는 “이 메서드가 호출되었는지” 같은 동작만 검증할 수 있다.
➡️ Mock을 사용하면 이런 복잡한 의존성 없이,
테스트하고 싶은 코드만 독립적(isolated) 으로 검증할 수 있다.
즉, “단위 테스트”의 핵심 도구.
*단위테스트 : 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트, 나중에 이에 대해서도 포스팅 할 예정.
Mock 객체의 정의와 특징 :
- 모의 객체 (Mock Object) 란, 실제 객체의 행동을 흉내내는 가짜 객체(Test Double)로, 테스트 대상 코드의 동작 검증을 위해 만들어진다.
- 행위 검증에 집중, 메서드가 어떻게 호출 / 몇 번 호출 되는지 , 어떤 인자가 전달되는지 검증이 가능하다.
- Mock 은 테스트 중 예상되는 동작을 정의(stubbing)하고, 검증까지 가능하다.
- 수동으로 구현할 수 있지만, 주로 Mockito와 같은 프레임워크로 생성하며, 모든 메서드를 stub 처리해서 사용할 수 있다.
- Dummy: 전달만 하고 쓰진 않음(널 채우기용)
- Fake: 간이 구현(예: 메모리 DB)
- Stub: “질문에 답만 하는” 고정 응답 객체
- Mock: 호출 자체(행동)를 검증하기 위한 객체
- Spy: 실제 객체를 감싸 일부는 진짜로 돌리고 일부만 가로채는 부분 모킹
핵심 차이: Stub은 “무엇을 돌려줄지”, Mock은 “어떻게 불렸는지(행동)”까지 검증.
Mock 객체의 활용 목적 :
- 의존성 격리: DB, 네트워크, 파일, 외부 API 같은 불안정/느린 요소 제거
- 단위 테스트 독립성 보장 : 외부 시스템과 무관하게 코드 단위만 검증가능.
- 재현성/속도: 테스트를 빠르고 결정적으로
- 테스트 속도 향상 : 실제 객체보다 가벼운 mock 을 사용해 반복 실행을 빠르게 처리.
- 행동 검증: “정말 이 메서드를 이렇게 호출했나?”를 직접 확인
- 특정 상황 시뮬레이션 : 네트워크 장애, 비정상 입력 등 실제 환경에서 구현 어려운 상황을 재현.
- 경계 조건 테스트: 성공/예외/타임아웃 등 다양한 시나리오를 쉽게 만들기
- 개발 단계에서 대상 코드의 의존성이나 외부 컴포넌트가 준비되지 않았을 때 임시로 사용가능.
Mock 객채와 행위 기반 테스트 :
- Mock 객체는 행위 기반 테스트에서 많이 사용한다.
- 어떤 동작이 실제로 발생했는지, 올바른 인자가 전달되었는지 등 상태가 아니라 "실행자체"에 집중한다.
- 예시 : 특정 서비스의 메서드가 몇 번 호출 되었는지 검증하는 등.
Mockito 를 통한 Mock 객체 사용 예시 :
// Mock 객체 생성
List<String> mockList = Mockito.mock(List.class);
// 예상 동작(stubbing) 설정
Mockito.when(mockList.size()).thenReturn(3);
// 메서드 테스트
int size = mockList.size(); // 3 반환
// 행위 검증
Mockito.verify(mockList).size();
| @Mock | Mock 객체 자동 생성 |
| @InjectMocks | Mock 객체를 테스트 대상에 자동 주입 |
| Mockito.verify() | 행위 검증 (메서드 호출 체크) |
Mockito 에 대해 함 알아보자~!
#2. Mockito
Mockito는 Java에서 Mock 객체를 쉽게 생성하고 검증할 수 있게 해주는 라이브러리
대표적인 Mock 프레임워크로, Spring Boot나 JUnit 테스트에서 거의 표준처럼 쓰임
1) 📘 주요 기능 예시
Mock 생성
EmailService emailService = mock(EmailService.class);
when(emailService.sendWelcomeEmail("test@example.com")).thenReturn(true);
when( mockObject.someMethod(param) ).thenReturn(value);
“mockObject의 someMethod(param)이 호출되면 value를 반환해라.”
List<String> mockList = mock(List.class);
// 리스트의 get(0)이 호출될 때 "hello" 반환
when(mockList.get(0)).thenReturn("hello");
// 테스트 코드
System.out.println(mockList.get(0)); // 출력: hello
System.out.println(mockList.get(1)); // 출력: null (stub 안 했으므로 기본값)
then 의 옵션은 여러가지가 있다.
- thenReturn(value) : 지정된 값을 반환
- thenThrow(exception) : 예외를 던짐
- thenAnswer(answer) : 호출 시 동적으로 결과 계산
- thenCallrealMethod() : mock 이 아니라 진짜 메서드 실행 ( spy 에서 주로 사용 )
Mock 호출 검증
verify(emailService).sendWelcomeEmail("test@example.com");
JUnit과 함께 사용
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
EmailService emailService;
@InjectMocks
UserService userService;
@Test
void testRegisterUser() {
userService.registerUser("abc@example.com");
verify(emailService).sendWelcomeEmail("abc@example.com");
}
}
인자 매칭(Argument Matchers)
any(), eq() 같은 matcher 를 지원해서 인자를 유연하게 매칭할 수 있다.
when(mockList.get(anyInt())).thenReturn("mocked!");
System.out.println(mockList.get(10)); // "mocked!"
matcher 의미
| anyInt() | 아무 int 값 |
| anyString() | 아무 문자열 |
| any(Class.class) | 아무 객체 |
| eq(value) | 특정 값과 정확히 일치 |
2) Mockito에서 Mock의 핵심 사용법
1. 생성/주입
@ExtendWith(MockitoExtension.class) // JUnit5 예시, TestNG도 유사
class OrderServiceTest {
@Mock PaymentGateway paymentGateway; // 의존성
@Mock InventoryRepository inventory; // 의존성
@InjectMocks OrderService orderService; // 테스트 대상 (의존성 자동 주입)
}
2. Stubbing(응답 정의)
when(paymentGateway.pay(any())).thenReturn(Receipt.ok());
when(inventory.reserve(eq("ITEM-1"), anyInt())).thenReturn(true);
// void 메서드는 doThrow / doNothing / doAnswer 사용
doThrow(new IOException("down")).when(paymentGateway).refund(any());
**Spy에서는 실제 호출이 일어나는 걸 막기 위해 doReturn(...).when(spy).method() 권장.
ex)
List<String> spyList = spy(new ArrayList<>());
// ✅ 안전한 방식 — 실제 호출 발생하지 않음
doReturn("mocked").when(spyList).get(0);
System.out.println(spyList.get(0)); // "mocked" 출력, 예외 없음
3. Verification(호출 검증)
orderService.place("ITEM-1", 2);
verify(inventory).reserve("ITEM-1", 2); // 호출 여부
verify(paymentGateway, times(1)).pay(any()); // 횟수
verify(paymentGateway, never()).refund(any()); // 호출 안 됨
verifyNoMoreInteractions(inventory, paymentGateway); // 그 외 없음
행동 검증이 Mock의 존재 이유. (state 검증은 assert로 객체 상태 확인)
4. ArgumentCaptor(인자 포착)
ArgumentCaptor<PaymentRequest> captor = ArgumentCaptor.forClass(PaymentRequest.class);
verify(paymentGateway).pay(captor.capture());
assertEquals(2, captor.getValue().quantity());
→ 호출됐을 때 무슨 값이 넘어갔는지까지 확인 가능.
5. BDD 스타일
given(inventory.reserve("ITEM-1", 2)).willReturn(true);
orderService.place("ITEM-1", 2);
then(paymentGateway).should().pay(any());
가독성: Given–When–Then 흐름에 잘 맞음.
*BDD 역시 다음 글에 적어볼예정..
#3. TestNG + Mockito 버전으로
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
public class UserServiceTest {
@Mock
private EmailService emailService; // 모의 객체(Mock)
private UserService userService; // 테스트 대상
private AutoCloseable mocks; // openMocks() 닫기용
@BeforeClass
public void setUp() {
// Mock 초기화
mocks = MockitoAnnotations.openMocks(this);
userService = new UserService(emailService);
}
@Test
public void testRegister_withValidEmail_shouldSendEmailAndReturnSuccess() {
// Given
when(emailService.isValid("test@example.com")).thenReturn(true);
// When
String result = userService.register("test@example.com");
// Then
assertEquals(result, "success");
verify(emailService, times(1)).send("test@example.com");
}
@Test
public void testRegister_withInvalidEmail_shouldReturnFail() {
// Given
when(emailService.isValid("invalid-email")).thenReturn(false);
// When
String result = userService.register("invalid-email");
// Then
assertEquals(result, "fail");
verify(emailService, never()).send(anyString());
}
@AfterClass
public void tearDown() throws Exception {
// Mockito context 해제 (권장)
mocks.close();
}
}
- @BeforeClass
→ Mockito mock 초기화 및 UserService 생성 - @Test #1
→ 유효한 이메일 시나리오: isValid() → true, send() 호출 확인 - @Test #2
→ 잘못된 이메일 시나리오: isValid() → false, send() 호출 안 됨 - @AfterClass
→ mocks.close()로 context 정리
여담)
구수한 상인같은 gpt

정리해드릴깝쇼? 이런느낌이라서 웃겨서 남김
'☁️2024,2025☁️ > Test&Dev' 카테고리의 다른 글
| [Test_그래도해야지어떡해] #1. TestNG 란? _ docs 번역 및 정리 (0) | 2025.11.08 |
|---|