새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
TIL) 테스트 코드를 작성하면서
📌 2025-03-27 TIL
1. 오늘의 학습 주제
- 모킹과 트랜잭션의 관계
2. 학습 내용
저는 @SpyBean
과 @Transactional
의 순서를 잘못알았습니다.
Junit은 테스트 프레임워크로, @Test 같은 어노테이션이 붙은 메서드를 실행하며, Mockito 같은 라이브러리와 통합해 모의 객체를 생성할 수 있습니다.
스프링 컨테이너를 활용한 테스트를 위해서는 @SpringBootTest에 설정 클래스(기본값: xxxApplication.class)와 클래스 정보를 제공해 컨테이너를 초기화합니다.
@SpringBootTest는 기본적으로 xxxApplication.class를 사용해 스프링 컨테이너를 초기화하며, 스프링 부트는 빈을 생성하고 자동 등록된 BeanPostProcessor를 통해 의존성 주입(@Autowired)과 AOP(@Transactional)를 적용합니다.
기본적으로 테스트 클래스마다 @SpringBootTest가 있으면 매번 새 컨테이너를 초기화하므로 시간이 오래 걸립니다. 테스트 컨텍스트 캐싱을 통해 동일한 설정을 재사용하면 초기화 횟수를 줄여 테스트 속도를 높일 수 있습니다.
테스트 컨텍스트 캐싱은 상속으로 공통 @SpringBootTest 부모 클래스를 정의해 동일한 컨텍스트를 공유하는 방식입니다. 하위 클래스에서 다른 @MockBean이나 @SpyBean을 추가하면 새 컨텍스트가 생성되니, 공통 빈은 부모 클래스에 정의해야 합니다.
모킹을 위해 모의 객체를 등록하려면, 테스트 환경에서 MockitoPostProcessor가 동작합니다. @SpyBean 같은 어노테이션을 감지해 postProcessBeforeInitialization에서 원본 객체를 스파이로 감싸고, 이후 postProcessAfterInitialization에서 AOP(예: 트랜잭션 프록시)가 스파이를 감쌉니다.
실제로 @SpyBean
이나 @MockBean
이나 모두 @AOP
가 적용됩니다.
트랜잭션 입장에서는 내가 호출하는 객체가 모의 객체 빈인지, 원본 객체 빈인지 알 수 없습니다.
그래서 아래 코드를 통해 확인해보면 트랜잭션이 동작하는 것을 확인할 수 있습니다.
@Test
public void testTransactionManagerActiveWithMocking() {
Product product = Product.builder().productNumber("001").name("아메리카노").build();
Order order = Order.builder().orderStatus(OrderStatus.INIT).build();
// saveOrder 모킹 + 트랜잭션 매니저 상태 확인
doAnswer(invocation -> {
boolean isTxActive = TransactionSynchronizationManager.isActualTransactionActive();
System.out.println("트랜잭션" + isTxActive);
throw new IOException("");
// 여기서 DB 작업 없이 단순 확인만
}).when(productService).saveOrder(any(Order.class));
// 실행
productService.saveProductAndOrder(order, product);
// 검증
verify(productService).saveOrder(order);
}
댓글남기기