Mockito를 사용한 테스트 코드
Mockito를 사용한 테스트 코드 알아보기
우선 TDD(Test Driven Development)는
테스트가 개발을 주도 한다는 개념이다.
Mockito란?
Mock(진짜 객체 처럼 동작하지만 프로그래머가 직접 컨트롤 할 수 있는 객체)을 지원하는 프레임워크. Mock 객체를 만들고 관리하고 검증 할 수 있는 방법 제공한다. (가짜 객체를 만들어준다 느낌)
테스트 코드의 given when then 의 의미
1. given
보통 '준비'이라는 의미로 많이 얘기함
테스트를 실행하기 위한 초기 상태나 조건을 설정하는 단계
2. when
보통 '진행' 이라는 의미로 많이 나오는데
테스트 대상 메서드(또는 기능)를 실제로 실행하는 단계이다.
3. then
보통 검색하면 '검증' 이라고 표시하는데
앞선 과정의 결과의 검증 이라는 의미이다.
테스트 코드가 어떻게 이루어 지는지 확인해보자
다음은 예시 코드이다.
@Mock 어노테이션으로 가짜 객체를 생성했다.
@ExtendWith(MockitoExtension.class)
class CouponRedisServiceTest {
@InjectMocks
private CouponRedisService couponRedisService;
@Mock
private RedissonClient redissonClient;
@Mock
private CouponRepository couponRepository;
@Mock
private CouponPolicyService couponPolicyService;
@Mock
private RLock rLock;
@Mock
private RAtomicLong atomicLong;
private CouponPolicy couponPolicy;
private Coupon coupon;
private static final Long TEST_USER_ID = 1L;
private static final Long TEST_COUPON_ID = 1L;
private static final Long TEST_POLICY_ID = 1L;
@BeforeEach
void setUp() {
couponPolicy = CouponPolicy.builder()
.id(TEST_POLICY_ID)
.name("테스트 쿠폰")
.discountType(CouponPolicy.DiscountType.FIXED_AMOUNT)
.discountValue(1000)
.minimumOrderAmount(10000)
.maximumDiscountAmount(1000)
.startTime(LocalDateTime.now().minusDays(1))
.endTime(LocalDateTime.now().plusDays(1))
.build();
coupon = Coupon.builder()
.id(TEST_COUPON_ID)
.userId(TEST_USER_ID)
.couponPolicy(couponPolicy)
.couponCode("TEST123")
.build();
}
// ... 여기에 테스트 코드를 작성할 거다. ...
@Test
@DisplayName("쿠폰 발급 성공")
void issueCoupon_Success() throws InterruptedException {
// Given
CouponDto.IssueRequest request = CouponDto.IssueRequest.builder()
.couponPolicyId(TEST_POLICY_ID)
.build();
when(redissonClient.getLock(anyString())).thenReturn(rLock);
when(rLock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true); // 락을 얻았다는 true 표시
when(rLock.isHeldByCurrentThread()).thenReturn(true);
when(redissonClient.getAtomicLong(anyString())).thenReturn(atomicLong);
when(atomicLong.decrementAndGet()).thenReturn(99L); // 한개 줄면 99개
when(couponPolicyService.getCouponPolicy(TEST_POLICY_ID)).thenReturn(couponPolicy); // 쿠폰 정책이 정상적으로 조회됨
when(couponRepository.save(any(Coupon.class))).thenReturn(coupon); // 쿠폰이 정상적으로 발급
try (MockedStatic<UserIdInterceptor> mockedStatic = mockStatic(UserIdInterceptor.class)) {
mockedStatic.when(UserIdInterceptor::getCurrentUserId).thenReturn(TEST_USER_ID);
// When
Coupon coupon = couponRedisService.issueCoupon(request);
// Then
assertThat(coupon.getId()).isEqualTo(TEST_COUPON_ID);
assertThat(coupon.getUserId()).isEqualTo(TEST_USER_ID);
verify(couponRepository).save(any(Coupon.class));
verify(rLock).unlock();
}
}
}
given (조건 부분)
여기서 when( ... ).thenReturn 코드들은
실제 실행해서 진행하는게 아니라 비즈니스 로직이 그 상황에서 제대로 동작하는지 테스트하는게 목적이기 때문에
결과를 강제로 만들어서 해당 조건일때 조건을 설정하는 부분이다.
검증은 then 에서 결과가 맞는지 검증한다.
when(rLock.tryLock(...)).thenReturn(true); 은
테스트에서는 실제 락을 걸고 기다리는 게 아니라 이런 상황이라고 가정하는 것이다.
ex)
when(...).thenReturn(true) 은 해당 메서드가 호출되면 true 리턴하도록 강제한다.
when(...).thenReturn(false) 은해당 메서드가 호출되면 false 리턴하도록 강제한다.
when (실행 부분)
Coupon coupon = couponRedisService.issueCoupon(request);
실제 실행하는 코드이다.
then (검증 부분)
verify(couponRepository).save(any(Coupon.class));
verify(rLock).unlock();
verify() 는 어떤 메서드가 실제로 호출됐는지를 확인한다.
couponRepository의 save 메서드가 한 번은 호출됐는지 검증한다.
any() 는 어떤 Coupon 객체라도 괜찮다는 의미이다.
또한 락을 얻었으면 끝나고 락을 해제해야하기 때문에 실제로 해제 했는지 검증한다.