LocalDate 테스트 하기 (LocalDate를 mock 해보자)

발단

얼마전 LocalDate를 이용한 테스트 케이스를 작성하다가 고민이 생겼습니다.
테스트할 함수는 파라미터로 받은 LocalDate를 오늘날짜와 비교하는 함수였는데요 아래는 샘플코드입니다.

1
2
3
4
5
public boolean isBeforeTarget(LocalDate targetDate) {

//작성일 기준으로 LocalDate.now()는 2021-09-07입니다.
return LocalDate.now().isBefore(targetDate);
}

날짜를 받고, 오늘 날짜랑 비교해서 오늘날짜가 targetDate 이전이면 true, 이후면 false를 리턴하게 되는데.
테스트 코드는 아래와 같습니다.

1
2
3
4
5
6
7
private Utils utils = new Utils();

@Test
void isBeforeTarget() {
LocalDate targetDate = LocalDate.of(2021, 9, 8);
assertThat(utils.isBeforeTarget(targetDate), is(true));
}

작성일 기준으로 오늘은 9월 7일이고, targetDate는 9월 8일이라 오늘은 targetDate 전 입니다. 그래서 true를 반환할거고 이 테스트코드는 통과하게 됩니다.

여기까지는 별 문제가 없었는데…

isBeforeTarget 함수에서 오늘을 기준으로 비교를 하다보니 이 테스트 코드는 내일부터는 fail로 바뀌게 될것입니다.
targetDate가 고정이라고 해도 LocalDate.now()가 매일매일 바뀔것이기 때문이죠.
그럼 테스트코드에서는 LocalDate.now()의 결과물이 항상 같은 날짜가 나오면 문제가 해결될거 같았습니다.

일반적으로 사용되는 @Mock 어노테이션을 이용해 LocalDate를 모킹하고 now 메소드 호출시에 임의의 날짜를 반환하게 작업하면 되겠지 라는 생각이었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

private final LocalDate NOW = LocalDate.of(2021, 9 ,1); // [1]

private Utils utils = new Utils();

@Mock
LocalDate localDate;

@Test
public void beforeTarget() {

// now 메소드가 호출되면 [1]에서 선언한 고정된 날짜를 반환하게 하고 싶었습니다.
// static 메소드라 LocalDate.now()를 호출해야하는데 mocking된 객체를 사용해야 할거 같았습니다.
when(localDate.now()).thenReturn(NOW);

LocalDate targetDate = LocalDate.of(2021, 9, 6);
assertThat(utils.isBeforeTarget(targetDate), is(true));
}

아래와 같은 에러가 발생을 했는데요;

1
2
3
4
5
6
7
8
9
10
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

mockito에서 when 메소드의 인자값으로 ‘호출이 되는 메소드’를 넣어줘야하는데 static 메소드라 정상적으로 메소드를 인식하지 못하는거 같았습니다.

찾아보니 mockito를 이용해 static 메소드를 직접 모킹하는건 불가능하고 PowerMockito를 이용해야 한다고 나와있었습니다.
(* Mockito 3.4.0 이후 버젼에서는 PowerMockito 없이도 static 메소드 모킹이 가능하다고 합니다. 참고

 
 

PowerMocito를 이용해보자!

PowerMocito는 PowerMock의 확장 API라고 합니다. 그래서 PowerMock과 PowerMock API Mockito를 둘다 추가를 해줘야합니다.

1
2
3
// build.gradle
testImplementation "org.powermock:powermock-module-junit4:2.0.9"
testImplementation "org.powermock:powermock-api-mockito2:2.0.9"

그리고 테스트 코드 클래스의 제일 상단에 아래 두개의 어노테이션을 추가해줘야합니다.

1
2
@RunWith(PowerMockRunner.class)  
@PrepareForTest({Utils.class}) // static 메소드가 호출 되는 클래스를 미리 준비하기 위함 (참고 3번째 링크)

기존 테스트코드에 LocalData를 모킹하는 코드를 추가해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

private final LocalDate NOW = LocalDate.of(2021, 9 ,1);

@Test
void isBeforeTarget() {

LocalDate targetDate = LocalDate.of(2021, 9, 8);

// Mocking
PowerMockito.mockStatic(LocalDate.class);
when(LocalDate.now()).thenReturn(NOW);

// 클래스를 통째로 mocking하지 않고 원하는 static 메소드만 처리하고 싶은경우
// PowerMockito.stub(PowerMockito.method(LocalDate.class, "now")).toReturn(NOW);

assertThat(utils.isBeforeTarget(targetDate), is(true));
}

이렇게 하면 언제 실행을 해도 now() 호출했을때 2021년 9월 1일로 고정이 되어서, 테스트코드가 항상 Pass 하는걸 확인 할 수 있습니다.

사용된 코드는 아주 간단한 예제로 LocalDate의 함수를 이용한 코드를 테스트 하였지만.
PowerMockito를 이용하면 다른 static 메소드나, private 메소드도 테스트가 가능할걸로 생각 됩니다.

 
 

참고

공유하기