티스토리 뷰

728x90

LocalDateTime이나 UUID, Random 등을 이용하는 경우 robust한 테스트를 위해서 값을 고정시킬 필요가 있다.

하지만 기본적으로 제공하는 mockito-core에서는 static 오버라이딩을 지원하지 않기에 다음과 같은 방법이 필요하다.

 

Dependency

static 메서드를 사용하기 위해 다음 dependency를 추가한다.

(2022/06/19일 기준 4.6.1이 최신 버전이다.)

 

만약 아래의 디펜던시를 추가하지 않고 static 메서드를 모킹하려는 경우 다음과 같은 에러 메시지를 만날 것이다.

The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks Mockito's inline mock maker supports static mocks based on the Instrumentation API. You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'. Note that Mockito's inline mock maker is not supported on Android.org.mockito.exceptions.base.MockitoException: The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks Mockito's inline mock maker supports static mocks based on the Instrumentation API. You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'. Note that Mockito's inline mock maker is not supported on Android.

 

Maven

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

 

Gradle

// https://mvnrepository.com/artifact/org.mockito/mockito-inline
testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.6.1'

 

 

 

방법

필자가 구현하고 싶은 기능은 비밀번호 초기화 요청이 오면 현재로부터 10분의 유효기간을 가지는 토큰을 유저의 이메일로 보내고 10분이 지났다면 토큰이 만료되어 사용할 수 없는 기능이었다.

 

 

구현하는 코드는 아래와 같이 매우 간단하다.

public void passwordTokenValidator(PasswordResetRequest passwordResetRequest) {

    PasswordResetToken passwordResetToken = passwordResetTokenRepository.findByEmailAndToken(passwordResetRequest.getEmail(), passwordResetRequest.getToken())
            .orElseThrow(() -> new TokenExpiredException("유효하지 않은 주소입니다. 다시 시도해주세요"));

    if (LocalDateTime.now().isAfter(passwordResetToken.getExpirationTime())) {
        passwordResetToken.setIsExpired(true);
        throw new TokenExpiredException("주소가 만료되었습니다");
    }
}

 

기존에 시도했던 방법은 다음과 같은데 당연히 동작하지 않았다. 기본적으로 mockito에서 static 메서드의 모킹을 지원하지 않기 때문이다.

위의 디펜던시를 받는다해도 아래의 코드는 동작하지 않는다!

when(LocalDateTime.now()).thenReturn(LocalDateTime.now().plusMinutes(10));

 

LocalDateTime을 모킹하며 동작하는 코드는 다음과 같다.

아래 코드의 동작에서 가장 중요한 지점은 thenReturn()에 할당하고자 하는 값을 try block 밖에 위치시키는 것이다.

@Test
void tokenIsNotValid_TryAfter10Minutes_ThrowTokenExpiredException_passwordTokenValidator() {

    when(passwordResetTokenRepository.findByEmailAndToken(email, token)).thenReturn(Optional.of(passwordResetToken));
    
    LocalDateTime defaultTime = LocalDateTime.now().plusMinutes(10);
    try (MockedStatic<LocalDateTime> mockedLocalDateTime = mockStatic(LocalDateTime.class)) {
        mockedLocalDateTime.when(LocalDateTime::now).thenReturn(defaultTime);
        assertThrows(TokenExpiredException.class, () -> authService.passwordTokenValidator(passwordResetRequest));
    }
}

 

 

만약 아래와 같이 try block 내에 바로 값을 할당한다면 아래와 같은 에러 메시지를 만날 것이다.

 

@Test
void tokenIsNotValid_TryAfter10Minutes_ThrowTokenExpiredException_passwordTokenValidator() {
    when(passwordResetTokenRepository.findByEmailAndToken(email, token)).thenReturn(Optional.of(passwordResetToken));

    try(MockedStatic<LocalDateTime> mockedLocalDateTime = mockStatic(LocalDateTime.class)) {
        mockedLocalDateTime.when(LocalDateTime::now).thenReturn(LocalDateTime.now().plusMinutes(10));
        assertThrows(TokenExpiredException.class, () -> authService.passwordTokenValidator(passwordResetRequest));
    }
}

 

Unfinished stubbing detected here:
-> at com.jay.shoppingmall.service.AuthServiceTest.tokenIsNotValid_TryAfter10Minutes_ThrowTokenExpiredException_passwordTokenValidator(AuthServiceTest.java:121)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();

 

 

mockStatic 메서드를 보면 아래와 같은 경고문이 적혀있다. 반드시 자원을 사용하고 나면 끊어야 한다는 것인데 만약 close를 하지 않으면 해당 쓰레드는 모킹된 static 메서드가 남아서 다른 테스트에도 영향을 미친다는 얘기이다.

Creates a thread-local mock controller for all static methods of the given class or interface. The returned object's MockedStatic.close() method must be called upon completing the test or the mock will remain active on the current thread.

 

이를 해결하기 위해서 try-with-resources의 패턴으로 작성된 것이다.

아래와 같이 try (...) 내에 자원의 할당을 위치시킨다면 끝나고 나서 자동으로 close()가 실행된다.

try (MockedStatic<LocalDateTime> mockedLocalDateTime = mockStatic(LocalDateTime.class)) {

 

 

마지막으로 시간의 경과를 확인하기 위해서 LocalDateTime을 사용하는 것은 UTC 정보를 가지고 있지 않는 값이기에 어떤 의미있는 로직을 갖지 못 한다.

LocalDateTime을 Instant로 변경시켜야 할 필요가 있다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함