단위 테스트의 본질 (Why)
단위 테스트(Unit Test)의 목표는 단 하나입니다.
“내가 만든 로직만 검증한다. (격리)”
Test Double이란?
Mock (가짜 객체), Stub (상태 고정), Spy (관찰자/스파이)
① Mock (가짜 객체)

UserRepository userRepository = mock(UserRepository.class);
userRepository.save(user);
② Stub (상태 고정)
특정 호출에 대해 미리 정해진 답을 내놓도록 만든 상태입니다.

"A라는 입력이 들어오면 B를 뱉어라"라고,
“이 호출에는 이 값을 반환해라”라는 상태 설정
Stub이 필요한 이유는 다음 로직으로 테스트를 진행하려면
반드시 "결과값"이 필요할 경우 입니다.
Mockito에서는 Mock 객체에 when().thenReturn()을
사용하는 행위 자체가 Stubbing입니다.
// save()가 호출되면, 실제 로직은 실행하지 말고, savedUser를 반환해라.
when(userRepository.save(any()))
.thenReturn(savedUser);
❗해당 코드가 필요한 이유는
userRepository.save(user) 코드를 그대로 실행하게 되면
DB 연결 시도, 트랜잭션 열림, INSERT SQL 실행 등
단위 테스트에서 절대 원하지 않는 일이기에
정해진 값을 반환해주는 것입니다.
✅ “Mock인 UserRepository에 Stubbing을 했다”
③ Spy (관찰자/스파이)

실제 객체를 감싸는 래퍼(Wrapper)입니다.
진짜 객체를 기반으로 만든 가짜
기본적으로 진짜 객체의 로직을 수행합니다.
하지만 특정 메서드만 가짜 답을 내놓게 하거나,
어떤 메서드가 몇 번 불렸는지 몰래 감시할 수 있습니다.
실제 로직을 써야 하지만 일부만 제어하고 싶을 때 사용합니다.
(정말 필요한 경우에만 사용)
// 실제 객체를 감싼 래퍼
// 기본 동작 = 진짜 로직 실행
Calculator calculator = spy(new Calculator());
// Spy 전용 문법
// 실제 메서드를 아예 타지 않게 막는 방식
// applyCoupon을 실행하지 말고, 그냥 1000을 반환해라
doReturn(1000)
.when(calculator)
.applyCoupon(any());
🚧 Spy가 위험한 이유
진짜 로직이 실행됨
의존성이 있으면 테스트가 깨짐
class Calculator {
private DiscountPolicy policy;
}
상태가 섞이기 쉬움
// 상태를 가진 클래스
class Counter {
int count = 0;
void increase() {
count++;
}
int getCount() {
return count;
}
}
Counter counter = spy(new Counter());
count는 0이고, 진짜 객체 1개 생성 되면서count가 증가하니 테스트는 독립적이여야 하는데어떤 상황에서 무엇을 써야할까?
회원가입 로직에서 UserRepository.save()를 호출해야 합니다.
하지만 진짜 DB에 저장할 필요는 없습니다.
선택: UserRepository를 Mock으로 만듭니다.
Stubbing: save()가 호출되면 가짜 User 객체를 리턴하라고 설정합니다.
검증: 최종적으로 verify(userRepository).save(any())를 통해
실제로 저장 메서드가 호출됐는지 확인합니다.
verify는 테스트 로직이 끝난 후에 호출됩니다.
아까 실행한 로직 중에 userRepository의 save가 실제로 호출됐는지 확인하는 것
결제 버튼을 눌렀을 때 외부 카드사 API가 호출되는지 확인해야 합니다.
선택: 결제 클라이언트를 Mock으로 만듭니다.
이유: 진짜 카드 결제가 일어나면 안 되기 때문입니다.
오직 "카드사 서버로 요청을 보냈는가?"만 확인합니다.
금액을 계산하는 Calculator 클래스가 있고, 이를 사용하는 OrderService를 테스트합니다.
계산 로직은 너무 정확해서 그대로 쓰고 싶은데,
특정 '이벤트 쿠폰' 적용 로직만 가짜로 처리하고 싶을 때가 있습니다.
상황 설명
Calculator라는 클래스 안에 메서드가 100개 있다고 가정
-> 99개는 너무 완벽하게 잘 만들어서 그대로 쓰고 싶음 (예: 덧셈, 곱셈, 세금 계산 등)
그런데 딱 1개, applyCoupon(쿠폰 적용) 메서드만 테스트하기가 까다롭습니다.
(예: 오늘 날짜에 따라 결과가 달라지거나, 외부 시스템을 확인해야 하는 경우)
이때 Mock을 쓰면 빈 껍데기라 99개의 완벽한 로직도 다 새로
가짜 답(Stub)을 만들어줘야 합니다. (번거로운 작업 필요)
이때 Spy를 쓰면?
나머지 99개는 그대로 사용하고, 쿠폰 적용만 내가 원하는 대로
설정이 가능합니다.
전체 로직 (실제): 주문금액 = (단가 * 수량) + 배송비
부분 로직 (가짜): - 할인금액(쿠폰)
실행 과정
Spy 생성: Calculator의 실제 인스턴스를 spy()로 감쌉니다.
조종(Stubbing): applyCoupon()이 불리면 무조건 5000을 반환하라고 명령합니다.
테스트 실행: OrderService에서 계산기를 돌려봅니다.
결과: 단가, 수량, 배송비 계산은 실제 코드가 돌아가서 정확한 값이 나오고,
마지막에 쿠폰 할인만 내가 설정한 5,000원이 딱 적용됩니다.
간단 요약
의존하는 객체가 너무 무겁거나(DB), 외부와 연결되어 있다면? → Mock을 만들어라
그 Mock이 특정 값을 반환해서 다음 로직을 진행시켜야 한다면? → Stubbing(when...thenReturn)
실제 로직을 돌려야 하는데, 딱 한 두 메서드만 가짜로 바꾸거나
호출 횟수를 재고 싶다면? → Spy를 사용하라