티스토리 뷰
테스트 코드를 작성하다 보면 자연스럽게 두 가지 선택지 사이에서 고민하게 됩니다. 하나는 Mockito를 활용한 단위 테스트이고, 다른 하나는 @SpringBootTest를 사용하는 통합 테스트입니다. 두 방식은 겉보기에는 모두 “테스트 코드”라는 동일한 목적을 가지고 있지만, 실제로는 해결하려는 문제의 범위와 철학이 다릅니다. 이번 글에서는 Mockito의 공식 문서를 기반으로, Mockito의 특징과 장단점을 정리하고, Spring Boot 환경에서 언제 Mockito 기반 단위 테스트를 선택하는 것이 적절한지, 그리고 @SpringBootTest와는 어떤 차이가 있는지 차분히 정리해 보려고 합니다.
Mockito란 무엇인가
Mockito는 Java 환경에서 널리 사용되는 mocking framework입니다. 공식 사이트에 따르면 Mockito는 테스트에서 의존 객체를 대체하기 위한 mock 객체를 쉽게 생성하고, 그 상호작용을 검증할 수 있도록 설계된 라이브러리입니다.
테스트 더블(Test Double)이라는 개념은 Martin Fowler가 정리한 용어로 잘 알려져 있습니다. 실제 객체 대신 테스트를 위해 사용하는 객체를 의미합니다. Mockito는 이 중에서도 주로 Mock과 Spy를 중심으로 기능을 제공합니다.
Mockito의 기본 철학은 비교적 단순합니다. 테스트 코드에서 협력 객체를 직접 생성하지 않고, 대신 mock 객체를 만들어 특정 동작을 정의하고, 그 객체와의 상호작용을 검증하는 방식입니다. 이를 통해 테스트 대상 클래스의 로직을 격리하여 검증할 수 있습니다.
Mockito의 설계 철학과 주요 특징
Mockito는 비교적 직관적인 API를 제공합니다. 대표적인 패턴이 when(...).thenReturn(...) 형태입니다. 특정 메서드 호출에 대해 어떤 값을 반환할 것인지 정의하는 방식입니다. 이 구조는 stub 동작을 명시적으로 정의하는 방식으로, 테스트 코드에서 의도를 비교적 명확히 드러낼 수 있습니다.
또 하나의 중요한 기능은 verify()입니다. 단순히 반환값을 검증하는 것이 아니라, 특정 메서드가 호출되었는지, 몇 번 호출되었는지 등을 검증할 수 있습니다. 이는 상호작용 기반 테스트(Interaction-based testing)라고 부를 수 있습니다. 상태 검증이 아니라 협력 객체와의 상호작용을 검증한다는 점에서 특징이 있습니다.
공식 GitHub 저장소와 문서를 보면, Mockito는 런타임에 동적으로 mock 객체를 생성합니다. 내부적으로는 바이트코드 조작 기술을 활용해 프록시 객체를 생성합니다. 개발자는 이를 직접 인식할 필요는 없지만, 이 구조 덕분에 인터페이스뿐 아니라 클래스 자체도 mock으로 만들 수 있습니다. 다만 final 클래스나 final 메서드에 대한 제약은 버전별로 차이가 있으므로, 공식 문서를 기준으로 확인하는 것이 안전합니다.
Mockito는 JUnit과 자연스럽게 통합됩니다. JUnit 5 환경에서는 @ExtendWith(MockitoExtension.class)를 사용하여 Mockito의 확장을 등록할 수 있습니다. 이를 통해 @Mock, @InjectMocks 어노테이션이 정상적으로 동작합니다. @InjectMocks는 테스트 대상 객체에 mock 객체를 자동으로 주입해 주는 기능을 제공합니다. 이 기능을 활용하면 수동으로 의존성을 세팅하는 반복 작업을 줄일 수 있습니다.
Spring Boot 환경에서의 Mockito 활용
Spring Boot 애플리케이션에서는 테스트 전략이 더 다양해집니다. 순수 단위 테스트를 작성할 수도 있고, Spring 컨텍스트를 로딩하는 테스트를 작성할 수도 있습니다.
Mockito를 활용하는 경우는 일반적으로 Spring 컨텍스트를 로딩하지 않는 단위 테스트입니다. 이 경우 테스트는 매우 빠르게 실행됩니다. ApplicationContext를 초기화하지 않기 때문에, 순수하게 자바 객체 단위에서 로직을 검증할 수 있습니다.
예를 들어 Service 계층을 테스트할 때, Repository를 mock으로 대체하면 DB에 접근하지 않고도 비즈니스 로직을 검증할 수 있습니다. 이 방식은 테스트의 격리성을 높이고, 외부 시스템에 대한 의존을 제거하는 데 효과적입니다.
Spring Boot에서는 @MockBean이라는 어노테이션도 제공합니다. 이는 Mockito의 @Mock과는 다릅니다. @MockBean은 Spring 컨텍스트에 mock 객체를 등록합니다. 즉, ApplicationContext는 로딩되지만, 특정 Bean을 mock으로 교체하는 방식입니다. 이 점에서 @Mock과는 적용 범위가 다릅니다. @Mock은 순수 Mockito 레벨에서 동작하며, Spring 컨텍스트와는 무관합니다.
이 차이를 이해하지 못하면, 테스트의 범위를 혼동하게 됩니다. 순수 단위 테스트인지, 슬라이스 테스트인지, 통합 테스트인지에 따라 선택해야 할 어노테이션이 달라집니다.
@SpringBootTest와의 차이
@SpringBootTest는 Spring Boot 공식 문서에 따르면 전체 ApplicationContext를 로딩하여 통합 테스트를 수행할 수 있도록 지원하는 어노테이션입니다. 이 방식은 실제 Bean 설정, 의존성 주입, 환경 설정 등을 모두 포함합니다.
Mockito 기반 단위 테스트와 비교했을 때 가장 큰 차이는 컨텍스트 로딩 여부입니다. Mockito 기반 테스트는 Spring을 전혀 사용하지 않을 수도 있습니다. 반면 @SpringBootTest는 애플리케이션을 거의 실제 구동 환경과 유사하게 초기화합니다.
이로 인해 테스트 속도에서 차이가 발생합니다. ApplicationContext 초기화는 비용이 큰 작업입니다. 테스트 수가 많아질수록 실행 시간에 영향을 줍니다. 반대로, @SpringBootTest는 실제 Bean 간 연결 상태를 검증할 수 있습니다. 설정 오류, 빈 등록 문제, 프로파일 설정 문제 등을 테스트 단계에서 발견할 수 있다는 장점이 있습니다.
또 하나의 차이는 테스트의 격리 수준입니다. Mockito 기반 테스트는 협력 객체를 모두 mock으로 대체할 수 있기 때문에, 순수하게 테스트 대상 클래스의 로직만 검증합니다. 반면 @SpringBootTest는 실제 Bean이 연결되므로, 여러 계층이 동시에 검증됩니다. 이는 통합 테스트에 가깝습니다.
테스트 피라미드 관점에서의 비교
테스트 피라미드 개념에서는 단위 테스트가 가장 많은 비중을 차지하고, 통합 테스트와 E2E 테스트는 상대적으로 적은 수를 유지하는 것이 일반적인 전략으로 소개됩니다.
Mockito는 단위 테스트를 작성하기에 적합한 도구입니다. 빠르고, 격리되어 있으며, 특정 클래스의 책임을 명확히 검증할 수 있습니다. 반면 @SpringBootTest는 통합 관점의 검증에 적합합니다. 실제 애플리케이션의 구성 요소들이 함께 동작하는지를 확인하는 데 의미가 있습니다.
이 둘은 경쟁 관계라기보다는 보완 관계에 가깝습니다. 단위 테스트만으로는 설정 오류를 발견하기 어렵고, 통합 테스트만으로는 세밀한 로직 검증이 어렵습니다.
Mockito의 장점과 한계
Mockito의 장점은 명확합니다. 테스트 실행 속도가 빠르고, 외부 의존성을 제거할 수 있으며, 특정 로직을 정밀하게 검증할 수 있습니다. 또한 상호작용 기반 검증을 통해 협력 객체와의 호출 관계를 확인할 수 있습니다.
다만, 과도한 mock 사용은 테스트를 구현 세부사항에 강하게 결합시킬 수 있습니다. verify() 호출이 지나치게 많아지면, 내부 구현 변경 시 테스트가 쉽게 깨질 수 있습니다. 이는 테스트가 동작의 결과가 아니라 내부 상호작용을 지나치게 검증하기 때문일 수 있습니다.
또한 Mockito는 어디까지나 단위 테스트 도구입니다. 실제 트랜잭션 설정, AOP 동작, Security 필터 체인 등은 검증하지 못합니다. 이러한 부분은 @SpringBootTest 또는 슬라이스 테스트가 더 적절할 수 있습니다.
언제 Mockito를 선택해야 하는가
Service 계층의 복잡한 비즈니스 로직을 빠르게 검증해야 하는 경우, 외부 API 호출이나 DB 접근을 배제하고 핵심 로직만 테스트하려는 경우에는 Mockito 기반 단위 테스트가 적절합니다.
특히 테스트 주도 개발을 적용할 때는 빠른 피드백이 중요합니다. 이때 ApplicationContext를 매번 로딩하는 것은 부담이 될 수 있습니다. Mockito는 이러한 상황에서 비교적 가벼운 테스트 환경을 제공합니다.
언제 @SpringBootTest가 더 적절한가
애플리케이션 설정이 올바르게 구성되었는지 검증해야 하는 경우, 여러 Bean이 실제로 연결되어 동작하는 흐름을 확인해야 하는 경우에는 @SpringBootTest가 더 적절할 수 있습니다.
예를 들어 트랜잭션 경계, JPA 설정, 실제 Repository 동작, 보안 설정 등은 mock으로는 충분히 검증하기 어렵습니다. 이런 경우 통합 테스트는 안정성을 높이는 데 기여합니다.
함께 사용하는 전략
실무에서는 한 가지 방식만 선택하기보다는 두 방식을 함께 사용하는 경우가 많습니다. 핵심 비즈니스 로직은 Mockito 기반 단위 테스트로 빠르게 검증하고, 주요 흐름에 대해서는 @SpringBootTest 기반 통합 테스트로 안정성을 확보하는 방식입니다.
이때 중요한 것은 테스트의 목적을 명확히 구분하는 일이라고 생각합니다. 이 테스트가 로직을 검증하기 위한 것인지, 설정과 연결을 검증하기 위한 것인지에 따라 선택이 달라집니다.
마무리하며
Mockito와 @SpringBootTest는 서로를 대체하는 도구라기보다는, 서로 다른 문제를 해결하기 위한 도구라고 정리할 수 있을 것 같습니다. Mockito는 단위 수준의 격리된 검증에 강점을 가지며, @SpringBootTest는 애플리케이션 전체 맥락을 확인하는 데 의미가 있습니다.
테스트 전략은 결국 팀의 품질 목표와 개발 속도, 유지보수 비용 사이의 균형 문제라고 생각합니다. 공식 문서를 기반으로 각 도구의 의도를 이해하고, 상황에 맞게 선택하는 것이 중요합니다. 저 역시 테스트 코드를 작성하면서, 단순히 커버리지를 채우는 것이 아니라 어떤 리스크를 줄이기 위한 테스트인지 고민하게 되었습니다.
Mockito를 사용할지, @SpringBootTest를 사용할지는 정답의 문제가 아니라 설계의 문제에 가깝습니다. 그 선택의 기준을 조금 더 명확히 정리해 두는 것이, 장기적으로는 코드 품질을 높이는 데 도움이 된다고 생각합니다.
'STUDY' 카테고리의 다른 글
- Total
- Today
- Yesterday
- 트래픽 처리
- Redis 성능 개선
- 백엔드 성능 튜닝
- Double-Checked Locking
- 트랜잭션 관리
- DB 트랜잭션
- Spring Batch
- 캐시 장애
- Cache Penetration
- Enum 기반 싱글톤
- 백엔드 성능
- Eager Initialization
- 스레드 생명주기
- Redis 캐시 전략
- TTL 설계
- 캐시 성능 비교
- DB 인덱스 성능
- InterruptedException
- Java Performance
- Cache Aside
- 캐시와 인덱스
- Cache Avalanche
- 백엔드 아키텍처
- 백엔드 성능 설계
- spring batch 5
- Hot Key 문제
- 동시성처리
- Redis vs DB
- Initialization-on-Demand Holder Idiom
- mybatis
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |

