문제상황
테스트 코드 작성 중 @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은 가능하다)
'테스트' 카테고리의 다른 글
컨트롤러 단위 테스트 시 mock 객체 stubbing을 해야할까 (1) | 2024.12.17 |
---|---|
@ParameterizedTest를 사용해보자 (0) | 2024.10.31 |
테스트 더미데이터 삽입 방법 (2) | 2024.10.21 |
테스트 환경 (@SpringBootTest와 @WebMvcTest) (1) | 2024.09.08 |
댓글