[TIL] 트러블슈팅: 비관적 락(Pessimistic Lock)과 Mockito 테스트의 함정

김재진·2026년 3월 17일

내일배움캠프

목록 보기
59/70

🚨 문제 상황

결제/환불 시스템에서 동시성 이슈(중복 결제, 중복 환불, 잔액 불일치 등)를 방어하기 위해 JPA의 비관적 락(Pessimistic Lock) 을 적용했다.
그런데 내가 작성한 Mockito 기반 테스트에서는 기대했던 “락 대기/차단” 동작이 전혀 나타나지 않았고, 마치 동시성 문제가 해결되지 않은 것처럼 보였다.


🔍 원인 분석 (핵심)

결론부터 말하면:

비관적 락은 “실제 DB에 쿼리가 나가고 트랜잭션이 걸릴 때”만 의미가 있는데, Mockito Mock은 DB를 거치지 않기 때문에 락을 검증할 수 없다.

1) @Lock(PESSIMISTIC_WRITE)는 DB 락으로 번역되는 기능

Spring Data JPA에서 @Lock(LockModeType.PESSIMISTIC_WRITE)를 적용하면, Hibernate/JPA가 실제 SQL을 실행할 때 DB 벤더에 맞게 락 쿼리로 반영된다.

  • 예: SELECT ... FOR UPDATE (DB에 따라 구문은 달라질 수 있음)

즉, 락은 “애플리케이션 코드 레벨”이 아니라 DB 트랜잭션/세션 레벨에서 발생하는 현상이다.

2) Mockito @Mock Repository는 “락의 전제 조건”을 모두 제거한다

@Mock 처리된 PaymentRepository는 다음을 수행하지 않는다.

  • 실제 SQL 실행 ❌
  • 실제 트랜잭션 참여 ❌ (DB 관점에서 의미 있는 트랜잭션)
  • 락 획득/대기/경합 ❌
  • 격리 수준/커넥션/세션 처리 ❌

Mock은 단지 “메서드를 호출하면 내가 지정한 값을 돌려주는 객체”일 뿐이다.
따라서 Mockito 기반 테스트에서 락 동작을 검증하려고 하면, 실제 DB 락을 검증하는 게 아니라 테스트 코드가 임의로 만든 시나리오(스텁 반환/호출 횟수) 를 검증하는 꼴이 된다.

3) 그래서 “테스트는 통과했는데 운영에서는 다름” 같은 불일치가 발생 가능

Mockito 테스트가 통과했다고 해도, 운영 환경에서는 아래 변수들이 결과를 바꿀 수 있다.

  • DB 종류(예: MySQL/InnoDB vs PostgreSQL)
  • 트랜잭션 격리 수준
  • 실제 쿼리 플랜/인덱스
  • 락 범위(레코드/갭락 등) 및 데드락 가능성
  • 트랜잭션 전파/경계 설정

💡 해결 방법

락을 검증하려면 Mock Repository를 제거하고, 실제 DB 트랜잭션이 동작하는 환경에서 테스트해야 한다.

선택지 A) @DataJpaTest로 Repository 레벨 락 검증

  • Repository 쿼리가 진짜로 나가고
  • @Lock이 DB에 반영되는지(락 대기/차단 상황 포함)
    를 검증할 수 있다.

다만 @DataJpaTest는 기본적으로 슬라이스 테스트라서 서비스/여러 빈이 필요한 케이스는 제한이 있을 수 있다.

선택지 B) @SpringBootTest로 서비스 + 트랜잭션 + 동시성 통합 검증

  • 실제 서비스 로직을 태운 상태에서
  • 멀티스레드/동시 요��을 만들어
  • “한 트랜잭션이 락을 잡고 있을 때 다른 트랜잭션이 대기/실패하는지”
    를 확인할 수 있다.

테스트 환경 팁 (보충)

  • H2 같은 인메모리 DB는 실제 운영 DB와 락 동작이 다를 수 있음
    → 가능하면 Testcontainers로 운영 DB와 같은 엔진을 붙여 테스트하면 신뢰도가 크게 올라간다.
  • 동시성 테스트는 ExecutorService, CountDownLatch, CyclicBarrier 등을 써서
    “동시에 시작”을 강제해야 재현성이 좋아진다.

🎓 배운 점 (정리)

  • 동시성 문제는 “단위 테스트 통과”보다 실제 DB를 포함한 통합 테스트가 훨씬 신뢰도가 높다.
  • 테스트 도구는 만능이 아니며, 검증하려는 대상이 무엇인지(비즈니스 로직 vs DB 락/트랜잭션) 에 따라 테스트 전략을 분리해야 한다.

✅ 테스트 역할 분리

  • 단위 테스트(Unit Test)

    • Mockito로 서비스의 순수 비즈니스 로직, 분기 조건, 예외 처리 검증
    • 빠르고 안정적이지만 DB 락/트랜잭션을 검증할 수는 없음
  • 통합 테스트(Integration Test)

    • 실제 DB를 활용해 락(Lock), 트랜잭션, 동시성 제어 검증
    • 느리지만 운영 환경과의 정합성이 높음

🔚 한 줄 요약

Mockito로는 DB 락을 테스트할 수 없다. 비관적 락 검증은 반드시 실제 DB 기반 통합 테스트로 해야 한다.

profile
개발공부 처음해보는 사람

0개의 댓글