ArgumentCaptor를 사용해 method 인자 값 검증하기

ArgumentCaptor란?

ArgumentCaptor는 Mockito 프레임워크에서 클래스로, Mock 객체의 메소드가 호출될 때 전달되는 인자를 이름 그대로 "캡처"하고 검증하는 데 사용된다.

ArgumentCaptor는 복잡한 객체나 람다 함수와 같은 인자를 검증할 때 매우 유용하게 사용할 수 있다.

 

ArgumentCaptor Mokito 공식 문서

https://site.mockito.org/javadoc/current/org/mockito/ArgumentCaptor.html

 

ArgumentCaptor (Mockito 2.2.7 API)

Use it to capture argument values for further assertions. Mockito verifies argument values in natural java style: by using an equals() method. This is also the recommended way of matching arguments because it makes tests clean & simple. In some situations

site.mockito.org


ArgumentCaptor로 method 인자 값을 가로채어 테스트 해보자.

 

필자의 경우에는 약속 방이 생성되고 난 뒤에 약속 시간 30분 전이되면 ETA(도착 예정 시간) 리스트가 조회 가능하다는 공지 알림이 필요했다.

따라서 이 기능 추가를 위해 약속 방이 생성되면, TaskScheduler로 약속 시간 30분 전에 FCM에 알림을 보내는 작업을 예약하였고,

이 기능을 테스트하고 싶었다.

 

TaskScheduler에 대해 궁금하다면 아래 링크를 참고하자.

2024.07.21 - [◼ JAVA/Spring] - [Spring] 사용자별 출발 시간 알림 예약 전송 기능 구현

 

[Spring] 사용자별 출발 시간 알림 예약 전송 기능 구현

현재 진행하고 있는 "원만한 친구 사이를 위한 약속 지킴이 서비스" 오디(ody) 프로젝트는친구들이 약속에 지각하지 않도록 돕기 위해 친구들의 출발 위치와 만나기로 약속한 장소까지 걸리는 소

hstory0208.tistory.com

 

만약 30분 후에 작업이 예약되고 이 때 원하는 작업이 잘 실행됐는지 테스트하기 위해서

테스트 코드를 실행하는데 30분을 기다릴 순 없는 노릇이다..

필자는 원하는 시간에, 원하는 작업이 잘 실행되는지에 대한 부분을 검증하기 싶었기 때문에 ArgumentCaptor를 사용해 테스트할 수 있었다.

30분전 ETA 목록 조회 가능 공지 테스트 코드
@DisplayName("약속 생성 후 약속 시간 30분 전에 ETA 공지 알림이 예약된다.")
@Test
void saveAndScheduleEtaNotice() {
    LocalDateTime meetingDateTime = TimeUtil.nowWithTrim().plusMinutes(40);
    MeetingSaveRequestV1 request = new MeetingSaveRequestV1(
            "데모데이 회식",
            meetingDateTime.toLocalDate(),
            meetingDateTime.toLocalTime(),
            Fixture.TARGET_LOCATION.getAddress(),
            Fixture.TARGET_LOCATION.getLatitude(),
            Fixture.TARGET_LOCATION.getLongitude()
    );
    meetingService.saveV1(request);

    ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
    ArgumentCaptor<Instant> timeCaptor = ArgumentCaptor.forClass(Instant.class);

    Mockito.verify(taskScheduler).schedule(runnableCaptor.capture(), timeCaptor.capture());
    Instant scheduledTime = timeCaptor.getValue();
    runnableCaptor.getValue().run();

    assertAll(
            () -> assertThat(meetingDateTime.minusMinutes(30).toInstant(KST_OFFSET)).isEqualTo(scheduledTime),
            () -> Mockito.verify(fcmPushSender, Mockito.times(1)).sendNoticeMessage(any(NoticeMessage.class))
    );
}

 

일단 코드를 간단하게 코드를 설명하면 meetingService.saveV1()가 호출되면 내부에서 taskScheduler.schedule()가 실행되는데

이 때 meeting 객체에 있는 약속 시간 30분전에 taskScheduler에 fcmPushSender.sendNoticeMessage() 로직을 예약한다.

 

taskScheduler 내부 작업이 의도한데로 잘 동작하는지 확인하기 위해 작성한 코드를 ArgumentCaptor 코드 라인 부터 한줄 씩 확인 해보자.

 

taskScheduler의 schedule()메서드에 사용되는 인자 타입들을 캡쳐할 ArgumentCaptor 객체 생성
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
ArgumentCaptor<Instant> timeCaptor = ArgumentCaptor.forClass(Instant.class);

 

meetingService.saveV1()가 호출 후에 실행됐어야할 해당 로직이 실행됐는지 검증함과 동시에 이 호출에 전달된 인자를 캡쳐
Mockito.verify(taskScheduler).schedule(runnableCaptor.capture(), timeCaptor.capture());

 

캡쳐해놓은 값을 반환 후 의도한데로 동작하는지 검증

약속을 현재 시간으로 부터 40분 후로 생성했고 ETA 공지 목록 확인 알림을 스케줄링 하는 작업 로직에선 이 약속 시간 30분 전으로 작업을 예약한다.

따라서 timeCaptor.getValue()로 얻은 scheduledTime이 지금으로 부터 10분 뒤인지를 검증하였고

runnableCaptor.getValue()로 부터 얻은 Runnable을 run()으로 실행해  fcmPushSender.sendNoticeMessage()이 실행됐는지 검증하였다.

Instant scheduledTime = timeCaptor.getValue();
runnableCaptor.getValue().run();

assertAll(
        () -> assertThat(meetingDateTime.minusMinutes(30).toInstant(KST_OFFSET)).isEqualTo(scheduledTime),
        () -> Mockito.verify(fcmPushSender, Mockito.times(1)).sendNoticeMessage(any(NoticeMessage.class))
);