본문 바로가기

Language & Framework/삽질기록

삽질 기록 (14) JPA DB테스트 시 @CreatedDate Mocking해서 테스트하기

 

내가 원하는 것은 아주 간단한 테스트였다.

 

given에서는 user에게 연관된 게시글을 총 15개 만들어서 저장하는데, 10개는 모두 같은 날짜의 게시글이고 나머지 5개는 이전 5일간의 게시글로 저장하는 것이다.

 

이후 이를 조회하면 날짜별로 집계되어 6개의 값을 가진 List가 반환되어야 한다.

즉,

Select ( count(article) , article.created_date)

from (article)

where article.id = userId

group by article.created_date

having article.created_date between(start, end)

이런 형태를 가진 아주 단순한 querydsl 쿼리문에 대한 테스트다.

 

근데 온갖 짓을 해봐도 테스트에 실패했다.

결과가 그냥 [ 15, 2022-11-23 ] 이렇게 grop by되지 않고 한 번에 카운트되어서 출력된다.

 

최근 내 쿼리 작성 능력이 아주 형편 없다는 것을 깨닫고 자신감이 떨어져 이 단순한 쿼리도 잘못 작성한 것인가에 대한 고민에 빠졌지만, 기능 테스트는 멀쩡하게 통과했다.

 

영문 모를 결과에 한참을 해메다가 로그를 읽어봤는데..

 

 

 

 

 

나의 노력이 무색하게 모든 쿼리가 오늘 날짜를 기준으로 날아가고 있었다.

@CreatedDate의 원리를 찾아봤는데, AOP를 이용해서 영속화 시점에 날짜를 직접 만들어서 넣어주고 저장 직전에 이를 반영한다.

 

그러면 그냥 저장한 뒤에 EntityManager를 flush, clear해서 강제로 중단시키고 다시 불러와서 Reflection으로 수정해버리면 되는 거 아닐까? 했지만 안된다.

 

이건 이유를 모르겠다.

단순히 값을 System.out.println으로 찍어보면 날짜가 바뀐 것으로 나오는데, 실제 update 쿼리는 날아가지 않는다.

단순히 JpaAuditing 소스코드만 봤을 때는 원인을 알 수 없었지만 일단 바쁘기 때문에 이건 포기하고 빠르게 다른 방법을 찾아냈다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
 
    @MockBean
    DateTimeProvider dateTimeProvider;
 
    @SpyBean
    AuditingHandler auditingHandler;
 
    @BeforeEach
    void setup() throws Exception {
        MockitoAnnotations.openMocks(this);
        auditingHandler.setDateTimeProvider(dateTimeProvider);
    }
 
 
cs

 

우선 DateTimeProvider한테 MockBean을 주입하고, AuditingHandler를 SpyBean으로 넣어준다.

그리고 스파이로 잠입시킨 auditingHandler를 이용해 DateTime을 제공하는 DateTimeProvider를 JpaAuditing에서 사용하는 날짜 제공자로 주입하면 된다.

 

그 뒤는 일반적인 테스팅 방법과 똑같다.

BDDMockito라면 given()을, 아니면 when()을 이용해서 모킹해주면 끝이다.

 

 

 

 

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
    @Test
    void getUserArticlesDataBetween() {
        // given
        User user = User.builder().build();
        entityManager.persist(user);
 
        BDDMockito.given(dateTimeProvider.getNow()).willReturn(Optional.of(LocalDateTime.of(2022,1,6,0,0,0)));
 
        for (int i=0; i<10; i++) {
            Article article = Article.builder().user(user).build();
            article.injectUserForMapping(user);
            entityManager.persist(article);
        }
        
        for (int i=1; i<=5; i++) {
            BDDMockito.given(dateTimeProvider.getNow()).willReturn(Optional.of(LocalDateTime.of(2022,1,i,0,0,0)));
            Article article = Article.builder().user(user).build();
            article.injectUserForMapping(user);
            entityManager.persist(article);
        }
        // when
        List<ActivityDto.Temporary> result = userQueryRepository.getUserArticlesDataBetween(january1st, december31st, user.getId());
 
        // then
        assertThat(result.size()).isEqualTo(6);
    }
 
cs

 

 

실제 사용 코드는 위와 같다.

이렇게 작성하면 가독성이 떨어지니까 Optional.of(LocalDateTime ~~~ )을 반환하는 메서드를 별도로 만드는 게 좋을 것 같다.

 

이런 걸로 몇 시간을 날린 건지.. 그래도 포기하고 대충 넘기지 않고 늘 끝까지 찾아서 해결하는 내 자신이 기특하다

ㅋㅋㅎ