테스트

테스트에서 @CreatedDate 정렬 문제 해결하기

메오백 2025. 2. 11.

문제상황

테스트 코드 작성 중 @CreatedDate 필드가 자동으로 현재 시간을 설정하면서, 데이터 정렬이 일정하지 않아 테스트가 실패하는 문제를 겪었다.

        Rating rating = saveRating(다미, 이준석, 3.5, 1);
        Rating rating1 = saveRating(레온이, 이준석, 3.5, 2);
        Rating rating2 = saveRating(장몽이, 이준석, 3.5, 3);
        Rating rating3 = saveRating(멍청이, 이준석, 3.5, 4);

        log.info("rating createdDate : {}",rating.getCreatedDate());
        log.info("rating 1 createdDate : {}", rating1.getCreatedDate());
        log.info("rating 2 createdDate : {}", rating2.getCreatedDate());
        log.info("rating 3 createdDate : {}", rating3.getCreatedDate());

생성 날짜 내림차순 정렬에 대해 rating3 ~ rating 순서로 정렬되기를 기대했지만, 매번 뒤죽박죽이었다. 

로그를 확인하면 rating1~3은 같은 시간에, rating은 나노초만 다른 시간에 생성되었다. @CreatedDate에 의해 자동으로 값이 들어가기 때문이다. 

        Rating rating = saveRating(다미, 이준석, 3.5, LocalDateTime.now());
        Rating rating1 = saveRating(레온이, 이준석, 3.5, LocalDateTime.now().plusDays(1));
        Rating rating2 = saveRating(장몽이, 이준석, 3.5, LocalDateTime.now().plusDays(2));
        Rating rating3 = saveRating(멍청이, 이준석, 3.5, LocalDateTime.now().plusDays(3));

        log.info("rating createdDate : {}", rating.getCreatedDate());
        log.info("rating 1 createdDate : {}", rating1.getCreatedDate());
        log.info("rating 2 createdDate : {}", rating2.getCreatedDate());
        log.info("rating 3 createdDate : {}", rating3.getCreatedDate());
        
        private Rating saveRating(Member member, Congressman congressman, double rate, LocalDateTime localDateTime) {
            return ratingRepository.save(Rating.builder().member(member).congressman(congressman).rate(rate)
                    .createdDate(localDateTime).build());
    	}

 

save 하기 전에 내가 원하는 대로 createdDate 값을 정해주면 어떨까? 

반영되지 않는 모습이다. 

이것은 ratingRepository.save() 하는 시점에 JPA Auditing이 동작하며  @CreatedDate에 의해 값이 들어가기 때문이다. 

해결방법

    @MockitoSpyBean
    AuditingHandler auditingHandler;

    @MockitoBean
    DateTimeProvider dateTimeProvider;

    @BeforeEach
    void setUp() {
        auditingHandler.setDateTimeProvider(dateTimeProvider);
    }

AuditingHandler 는 Spring Data JPA 에서 DateTimeProvider가 제공하는 날짜를 받아서 엔티티의 createdDate, modifiedDate 같은 필드를 채운다. 따라서 위와 같이 세팅해준다. 

    private Rating saveRating(Member member, Congressman congressman, double rate, int plusDays) {
        when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.now().plusDays(plusDays)));
        return ratingRepository.save(Rating.builder().member(member).congressman(congressman).rate(rate).build());
    }

이렇게 dateTimeProvider를 stubbing 해줘서 원하는 날짜로 세팅할 수 있다.

DateTimeProvider 는 SpyBean으로 사용할 수 없을까?

CongressmanRepositoryTest는 여러개의 테스트가 있고 그 중 하나의 테스트에서만 위 문제가 발생했다. 따라서 나는 해당 테스트에 대해서만 DateTimeProvider로 createdDate를 임의로 설정하고, 나머지 테스트에서는 원래대로 동작하길 원했다. 

@MockitoSpyBean
DateTimeProvider dateTimeProvider;

그래서 나는 DateTimeProvider를 @MockitoSpyBean 으로 사용하려고 했다. 

SpyBean은 stubbing 한 동작에 대해서는 stubbing 한 대로 동작하지만, 다른 동작에 대해서는 원래대로 동작하기 때문이다. 

하지만 DateTimeProvider는 기본적으로 Spring Context에서 자동으로 빈으로 등록되지 않아서 이렇게 SpyBean으로 생성할 수 없다. SpyBean은 기존에 등록된 스프링 빈을 감싸서 Spy 객체로 만드는 것이기 때문이다.

 

따지고 보면 DateTimeProvider를 @MockitoSpyBean 으로 사용하려는 고민을 할 필요는 없었다. 

1. createdDate에 영향을 받는 테스트 -> stubbing 해서 임의의 createdDate를 넣어줘야 함

2. createdDate에 영향을 받지 않는 테스트 -> stubbing 하지 않고 그냥 놔두면 null값이 들어감. 어차피 createdDate에 영향을 받지 않으니까 상관 없음 

즉 굳이 DateTimeProvider를 @MockitoSpyBean으로 사용할 필요는 없었고 그냥 해결방법 에서 제시한 대로 @MockitoBean 으로 사용하면 된다. (더해서 DateTimeProvider는 인터페이스인데 인터페이스는 spybean 으로 못만든다.  mockbean은 가능하다)

 

 

댓글