영호

[JPA] 테스트에서 repository.save()해도 createdAt이 null일 때 본문

JPA

[JPA] 테스트에서 repository.save()해도 createdAt이 null일 때

0h0 2023. 8. 6. 20:19

들어가면서,

repository단위 테스트를 작성하면서 save한 엔티티의 createdAt이 필요했는데, 해당 값이 계속 null인 문제를 해결한 과정입니다.
현재 createdAt은 @EntityListeners(AuditingEntityListener.class) 을 이용해 생성하고 있습니다.

문제 코드

@DataJpaTest
class CafePolicyRepositoryTest {

    @Autowired
    private CafePolicyRepository cafePolicyRepository;

    @Test
    void 특정_시간보다_이후에_생성된_카페_리워드_정책이_없으면_빈_리스트_반환() {
        Cafe savedCafe = cafeRepository.save(new Cafe());
        CafePolicy currentPolicy = cafePolicyRepository.save(new CafePolicy());

        System.out.println(currentPolicy.getCreatedAt()); // null
    }
}

제가 기대한건 cafePolicyRepository.save()를 하면서 엔티티의 createdAt이 생성되고, 이를 활용하길 원했습니다. 그러나 테스트가 계속 실패했고 원인을 살펴보니 createdAt에 null이 들어있었습니다.

해결 방법

해결방법은 간단합니다. 바로 Test클래스에 엔티티와 마찬가지로 @EntityListeners(AuditingEntityListener.class) 를 붙여주면 됩니다.

@EntityListeners(AuditingEntityListener.class)를 붙여주면 됩니다.
@DataJpaTest
class CafePolicyRepositoryTest {

    @Autowired
    private CafePolicyRepository cafePolicyRepository;

    @Test
    void 특정_시간보다_이후에_생성된_카페_리워드_정책이_없으면_빈_리스트_반환() {
        Cafe savedCafe = cafeRepository.save(new Cafe());
        CafePolicy currentPolicy = cafePolicyRepository.save(new CafePolicy());

        System.out.println(currentPolicy.getCreatedAt()); // 정상적으로 값이 들어감
    }
}

스택오버플로우를 보면 AuditingEntityListener는 @PrePersist, @PostPersist절에서 실행된다고 합니다. 실제 AuditingEntityListener코드를 살펴보겠습니다.

AuditingEntityListener

@Configurable
public class AuditingEntityListener {

    private @Nullable ObjectFactory<AuditingHandler> handler;

    @PrePersist
    public void touchForCreate(Object target) {
        Assert.notNull(target, "Entity must not be null");

        if (handler != null) {

            AuditingHandler object = handler.getObject();
            if (object != null) {
                object.markCreated(target);
            }
        }
    }

    @PreUpdate
    public void touchForUpdate(Object target) {
        Assert.notNull(target, "Entity must not be null");

        if (handler != null) {

            AuditingHandler object = handler.getObject();
            if (object != null) {
                object.markModified(target);
            }
        }
    }
}

touchForCreate을 보면 @PrePersist안에서 target과 handler에 대해 null검사를 하면서 실제 날짜 데이터를 넣어주는 것으로 추정되는 markCraeted() 를 호출합니다.

target과 handler에 어떤 값이 들어오는지 궁금해서 디버깅 해봤습니다.

AuditingEntityListener있는 상황

@EnableJpaAuditing
@DataJpaTest
class CafePolicyRepositoryTest {

    @Autowired
    private CafePolicyRepository cafePolicyRepository;

    @Autowired
    private CafeRepository cafeRepository;

    @Autowired
    private OwnerRepository ownerRepository;

    @Test
    void auditingTest() {
        ownerRepository.save(new Owner("name", "id", "pw", "phone"));
    }
}

target에는 저장하려는 엔티티, handler에는 targetBeanName이 jpaAuditingHandler 로 들어오는 것을 확인할 수 있었습니다.

AuditingEntityListener없는 상황

// @EnableJpaAuditing
@DataJpaTest
class CafePolicyRepositoryTest {

    @Autowired
    private CafePolicyRepository cafePolicyRepository;

    @Autowired
    private CafeRepository cafeRepository;

    @Autowired
    private OwnerRepository ownerRepository;
        @Test
    void auditingTest() {
        ownerRepository.save(new Owner("name", "id", "pw", "phone"));
    }
}

target에는 마찬가지로 저장하려는 엔티티, handler에는 null 이 들어오면서 날짜가 생성되지 않고 넘어가는 것을 확인할 수 있었습니다.

Comments