새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

1 분 소요

📌 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);
}

댓글남기기