결제/환불 시스템에서 동시성 이슈(중복 결제, 중복 환불, 잔액 불일치 등)를 방어하기 위해 JPA의 비관적 락(Pessimistic Lock) 을 적용했다.
그런데 내가 작성한 Mockito 기반 테스트에서는 기대했던 “락 대기/차단” 동작이 전혀 나타나지 않았고, 마치 동시성 문제가 해결되지 않은 것처럼 보였다.
결론부터 말하면:
비관적 락은 “실제 DB에 쿼리가 나가고 트랜잭션이 걸릴 때”만 의미가 있는데, Mockito Mock은 DB를 거치지 않기 때문에 락을 검증할 수 없다.
@Lock(PESSIMISTIC_WRITE)는 DB 락으로 번역되는 기능Spring Data JPA에서 @Lock(LockModeType.PESSIMISTIC_WRITE)를 적용하면, Hibernate/JPA가 실제 SQL을 실행할 때 DB 벤더에 맞게 락 쿼리로 반영된다.
SELECT ... FOR UPDATE (DB에 따라 구문은 달라질 수 있음)즉, 락은 “애플리케이션 코드 레벨”이 아니라 DB 트랜잭션/세션 레벨에서 발생하는 현상이다.
@Mock Repository는 “락의 전제 조건”을 모두 제거한다@Mock 처리된 PaymentRepository는 다음을 수행하지 않는다.
Mock은 단지 “메서드를 호출하면 내가 지정한 값을 돌려주는 객체”일 뿐이다.
따라서 Mockito 기반 테스트에서 락 동작을 검증하려고 하면, 실제 DB 락을 검증하는 게 아니라 테스트 코드가 임의로 만든 시나리오(스텁 반환/호출 횟수) 를 검증하는 꼴이 된다.
Mockito 테스트가 통과했다고 해도, 운영 환경에서는 아래 변수들이 결과를 바꿀 수 있다.
락을 검증하려면 Mock Repository를 제거하고, 실제 DB 트랜잭션이 동작하는 환경에서 테스트해야 한다.
@DataJpaTest로 Repository 레벨 락 검증@Lock이 DB에 반영되는지(락 대기/차단 상황 포함)다만
@DataJpaTest는 기본적으로 슬라이스 테스트라서 서비스/여러 빈이 필요한 케이스는 제한이 있을 수 있다.
@SpringBootTest로 서비스 + 트랜잭션 + 동시성 통합 검증ExecutorService, CountDownLatch, CyclicBarrier 등을 써서단위 테스트(Unit Test)
통합 테스트(Integration Test)
Mockito로는 DB 락을 테스트할 수 없다. 비관적 락 검증은 반드시 실제 DB 기반 통합 테스트로 해야 한다.