| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 엔티티 매니저
- tag
- 카카오사용자정보가져오기
- oAuth2
- button
- feignClient
- jenkins
- 인스턴스
- Spring API
- input
- 제로베이스
- 상속
- 백엔드스쿨
- 백엔드공부
- static
- form
- 카카오인증토큰받기
- Java
- GitHub_Actions
- html
- ci/cd
- MIND 2023 #후기
- 어떤 개발자?
- Interface
- spring
- 카카오인가코드받기
- Docker
- 예외
- 엔티티 생명주기
- 백엔드 로드맵
- Today
- Total
HiDevelop
Mockito에서 의존성 주입이 헷갈렸던 이유와 정리 본문
최근 회사에서 spring Boot 3.4로 버전업을 올리면서 테스트 코드가 꽤 많이 깨졌다.
그 과정에서 그동안 당연하게 쓰던 Mockito 패턴들이 사실은 정확히 이해되지 않은 채 사용되고 있었다는 걸 알게 됐다.
특히 아래 두 가지가 가장 헷갈렸다.
- Mockito는 어떤 기준으로 Mock을 주입하는가?
- 사실
@InjectMocks없이 테스트하는 게 더 좋은 구조 아닌가? - @InjectMocks, @Mock, @Spy 그리고 테스트 설계 기준
정리해보면 테스트 코드뿐 아니라 서비스 설계 방식까지 같이 돌아보게 되는 포인트였고, GPT 선생님께 혼나면서 얻은 지식들을 정리하고자 한다.
1. Mockito의 주입 우선순위
Mockito에서 @InjectMocks를 사용하면
테스트 클래스에 선언된 @Mock, @Spy들을 찾아 대상 객체에 주입해 준다.
이때 주입 우선순위는 명확하게 정해져 있다.
주입 우선순위
- 생성자 주입
- setter 주입
- 필드 주입 (리플렉션)
생성자 주입 (가장 먼저 선택됨)
Mockito는 가장 많은 파라미터를 받을 수 있는 생성자를 우선적으로 선택한다.
class OrderService {
private final OrderRepository repo;
private final PaymentClient client;
OrderService(OrderRepository repo, PaymentClient client) {
this.repo = repo;
this.client = client;
}
}@Mock OrderRepository repo;
@Mock PaymentClient client;
@InjectMocks OrderService service;이 경우는 굉장히 명확하다.
Mock 타입이 생성자 파라미터와 정확히 매칭되기 때문에 주입이 실패할 여지도 적다.
👉 그래서 생성자 주입이 가장 안전하다.
2️⃣ setter 주입
class OrderService { private OrderRepository repo; void setRepo(OrderRepository repo) { this.repo = repo; } }
setter가 있으면 Mockito는 setter를 통해 주입한다.
다만 이 방식은 의존성이 누락되어도 컴파일 타임에 알 수 없다는 단점이 있다.
3️⃣ 필드 주입 (최후의 수단)
class OrderService {
private OrderRepository repo;
}이 경우 Mockito는 리플렉션으로 강제로 주입한다.
동작은 하지만,
- 캡슐화가 깨지고
- IDE나 컴파일러의 보호를 전혀 받지 못한다
테스트가 “돌아간다”는 것 말고는 장점이 없다.
2. 생성자가 여러 개인 경우 주의할 점
class Service {
Service(Repo repo) {}
Service(Repo repo, Client client) {}
}Mockito는 파라미터가 더 많은 생성자를 선택한다.
문제는,
- 그 생성자에 필요한 Mock이 하나라도 없으면
- 해당 파라미터는
null로 들어간다
이런 구조는 테스트가 통과했다가도
어느 날 갑자기 NullPointerException으로 터질 수 있다.
3. 그런데… @InjectMocks는 꼭 필요한가?
결론부터 말하면,
잘 설계된 코드라면
@InjectMocks없이도 테스트가 가능하다.
그리고 그 방식이 오히려 더 낫다.
4. @InjectMocks 없이 테스트하는 가장 좋은 패턴
생성자를 직접 호출한다
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
OrderRepository repo;
OrderService service;
@BeforeEach
void setUp() {
service = new OrderService(repo);
}
}이 방식의 장점은 분명하다.
- 어떤 의존성이 필요한지 테스트 코드만 봐도 알 수 있고
- Mockito의 내부 주입 규칙을 몰라도 이해 가능하며
- 리팩토링에도 훨씬 안전하다
개인적으로는 이게 가장 이상적인 테스트 형태라고 생각한다.
Fake 객체를 사용하는 방식
간단한 의존성이라면 Mockito조차 필요 없다.
class FakeOrderRepository implements OrderRepository {
@Override
public Order save(Order o) {
return new Order(1L);
}
}@Test
void test() {
OrderService service = new OrderService(new FakeOrderRepository());
service.create();
}- 빠르고
- 결과가 결정적이며
- 테스트 의도가 명확하다
5. 그렇다면 @InjectMocks는 언제 쓰는 게 맞을까?
완전히 나쁜 건 아니다.
다만 선택지가 없을 때 쓰는 도구에 가깝다.
- 레거시 코드
- 필드 주입 구조
- 생성자 변경이 어려운 상황
이럴 때만 제한적으로 사용하는 게 맞다.
6. 정리하며
Mockito를 쓰다 보면
“어쨌든 돌아가니까”라는 이유로 패턴이 굳어지기 쉽다.
하지만 이번에 테스트를 정리하면서 느낀 건,
테스트 코드가 복잡하다면,
그건 테스트 문제가 아니라 설계 문제일 가능성이 높다는 점이었다.
- 생성자 주입
- 명시적인 의존성
- 테스트에서 직접 객체 생성
이 세 가지만 지켜도
Mockito는 훨씬 단순해지고, 테스트도 안정된다.
한 줄 요약
Mockito는 생성자 → setter → 필드 순으로 주입하며,
가장 좋은 테스트는@InjectMocks에 의존하지 않고
생성자를 직접 호출해 의존성을 명확히 드러내는 방식이다.
'Spring' 카테고리의 다른 글
| OAuth2 ( 실습 편) (1) | 2023.08.04 |
|---|---|
| 간단한 서버 간 통신 @FeignClient (0) | 2023.08.03 |
| OAuth2 (이론 및 준비) (0) | 2023.07.20 |
| ORM(Object Relational Mapping) (0) | 2023.04.09 |
| API 작성 (0) | 2023.03.19 |