| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- RDBMS
- kafka
- 트랜잭션
- 성능 최적화
- redis
- GitOps
- DevOps
- monitoring
- Kotlin
- 백엔드개발
- Java
- docker
- grafana
- prometheus
- CloudNative
- helm
- mysql
- 데이터베이스
- SpringBoot
- 백엔드
- spring boot
- netty
- 성능최적화
- NIO
- jvm
- JPA
- Kubernetes
- selector
- 동시성제어
- webflux
- Today
- Total
유성
Spring Boot 3.4+에서 @MockBean이 @MockitoBean으로 대체된 이유 본문
Spring Boot 3.4부터 기존의 @MockBean은 Deprecated되었고, 그 자리를 @MockitoBean이 대체하게 되었다.
단순히 이름만 바뀐 것이 아니라, 빈을 교체하는 방식 자체가 바뀌었다고 볼 수 있다.
왜 이런 변화가 일어났는지 내부 메커니즘을 중심으로 알아본다.
1 과거 @MockBean의 문제 '빈 등록 새치기'
@MockBean은 스프링 컨테이너가 뜨는 도중에 '기존 빈 정의'를 가로채서 Mock 객체로 바꿔치기하는 방식을 사용했다.
이 과정은 크게 다음과 같은 메커니즘으로 동작한다.
- 설계도(BeanDefinition) 조작: 빈이 실제로 생성되기 전, BeanFactoryPostProcessor 단계에서 기존 빈의 설계도를 삭제하거나 순위를 밀어낸다.
- CGLIB/ByteBuddy 프록시 생성: 메서드 시그니처만 남기고 내부 로직은 비워둔 채, given/willReturn 같은 Mockito의 기록 장치를 심은 프록시 객체를 만든다.
- 빈 등록 및 주입: 조작된 클래스를 빈으로 등록하여 테스트 코드에 주입한다.
너무 빨리 생성되는 빈들
스프링 내부에는 우리가 작성한 빈 외에도 수많은 인프라 빈들이 존재한다.
문제는 @MockBean 처리기가 설계도를 수정하기도 전에, 어떤 설정 클래스나 인프라 빈들이 "아직 가짜로 바뀌지 않은 진짜 설계도"를 보고 진짜 객체를 먼저 생성해버리는 경우가 생긴다는 점이다.
이미 진짜 객체가 메모리에 올라가 버리면, 나중에 설계도를 아무리 가짜로 바꿔봤자 의존성 그래프 곳곳에는 이미 진짜 객체의 참조가 전파된 후가 된다.
이 때문에 "Mocking은 분명히 했는데 왜 진짜 DB에 데이터가 쌓이지?" 같은 예측 불가능한 테스트 결과가 발생할 수 있다.
2. 컨텍스트 캐싱 문제
스프링 테스트 프레임워크는 테스트 속도를 높이기 위해 ApplicationContext를 재사용한다.
이때 설정 파일, 프로파일 그리고 @MockBean 목록이 포함된 캐시 키를 사용하고 키를 재사용하려 한다.
만약 테스트 클래스마다 @MockBean의 구성이 조금이라도 다르다면 어떻게 될까?
- Test A: Service1을 Mocking 함 -> Key A 생성
- Test B: Service2를 Mocking 함 -> Key B 생성
스프링은 이 두 테스트의 설정이 '다르다'고 판단하여, 기존 컨텍스트를 재사용하지 못하고 새로운 컨텍스트를 새로 띄우게 된다.
결과적으로 테스트 클래스가 100개인데 각각 @MockBean 구성이 다르다면, 스프링 배너가 100번 뜨면서 전체 테스트 시간은 기하급수적으로 늘어난다.
@MockBean은 기존 컨텍스트의 빈 정의(Bean Definition)를 강제로 수정하여 '변형된 상태'를 만든다. 이를 '오염(Dirtying)'이라 부른다.
즉, @MockBean 자체가 캐시 기능을 막는 것은 아니나, 테스트마다 달라지는 Mock 구성이 캐시 키를 파편화(Fragmentation)시켜 결과적으로 컨텍스트 재사용률을 떨어뜨리는 주범이 되었던 것이다.
3. 해결사로 등장한 @MockitoBean과 BeanOverride API
이러한 한계를 극복하기 위해 Spring Framework 6.2는 아예 BeanOverride라는 공식 API를 엔진(Core) 레벨에 도입했다.
엔진의 정식 기능: "Hack에서 Standard로"
이제 Spring Boot가 BeanFactoryPostProcessor를 이용해 외부에서 설계도를 몰래 바꾸는 '새치기' 방식은 사라졌다.
대신, 스프링의 핵심 엔진인 DefaultListableBeanFactory 자체가 빈을 생성하기 직전에 BeanOverrideProcessor를 호출한다.
엔진이 "이 빈은 오버라이드 대상인가?" 를 공식적으로 먼저 체크하고 생성 로직을 분기하기 때문에, 개입 시점이 훨씬 명확하고 안정적이다.
생명주기의 통합 "Early Bean문제 제거"
오버라이딩 로직이 빈 팩토리 엔진의 생성 라이프사이클 내부로 편입되었다.
덕분에 아무리 일찍 생성되어야 하는 인프라 빈이나 설정 클래스에서 호출하더라도, 엔진이 직접 "이 빈은 가짜(Mock)로 생성해야 함"을 인지하고 즉시 Mock 객체를 반환한다.
이로써 진짜 객체가 의도치 않게 먼저 태어나 의존성 그래프를 오염시키던 고질적인 타이밍 이슈가 원천적으로 봉쇄되었다.
최적화된 캐싱
과거 @MockBean 목록을 스프링 부트가 따로 관리하며 캐시 키를 계산했지만, 이제는 스프링 코어가 오버라이드 정보를 직접 관리한다.
- 공식적인 상태 관리: '오염(Dirtying)'이라는 부정확한 상태가 아니라, '공식적으로 특정 빈이 오버라이드된 상태'로 컨텍스트를 정의한다.
- 정교한 키 계산: 오버라이드된 빈의 메타데이터가 MergedContextConfiguration의 캐시 키 계산 로직에 정식적으로 포함된다. 덕분에 컨텍스트의 상태를 훨씬 더 명확하게 추적할 수 있으며, 불필요하게 캐시가 깨지거나 재생성되는 비용을 최소화한다.
확장성: @TestBean의 등장
BeanOverride API는 Mockito에 국한되지 않는다. 이 인프라를 활용해 @TestBean이라는 어노테이션도 함께 추가되었다.
- 수동 Mock의 표준화: 사용자 정의 가짜 클래스를 넣기 위해 @Primary나 @TestConfiguration을 복잡하게 설정할 필요가 없어졌다. @TestBean을 사용하면 개발자가 직접 만든 가짜 객체로 갈아끼우기가 가능하고, 프레임워크 표준 기능으로 들어왔다.
마치며
@MockBean이 스프링 부트가 제공하던 편리한 테스트 툴 이였다면,
@MockitoBean은 스프링 프레임워크가 공식적으로 닦아놓은 '전용 도구'라고 할 수 있다.
'테스트코드 & 정적분석' 카테고리의 다른 글
| [AI-Native] 테스트 전략: 개발자에서 설계자로의 전환 (0) | 2026.02.09 |
|---|---|
| IntelliJ Profiler로 보는 JVM 힙 & 스레드 덤프로 장애 상황 분석 (0) | 2026.01.30 |
| 테스트 코드 작성 2편 동시성 이슈와 외부 의존성 제어 (0) | 2025.12.23 |
| 테스트 코드 작성 1편 테스트가 고통스럽다면, 설계를 의심하라 (feat. POJO & Time) (0) | 2025.12.22 |
| 테스트 코드, '작동'을 넘어 '지속 가능한 시스템'으로 (0) | 2025.12.22 |