0. Intro
JPA Auditing 기능을 활용해서 생성일자, 수정일자와 같은 필드를 가지는 BaseEntity
클래스를 만들어두고, 모든 도메인이 해당 클래스를 상속하게끔 구현했다.
그런데, 이렇게 매핑해두었던 것이 Service 테스트 코드 작성 중에 문제가 조금 생겼다.
당시의 프로젝트 진행 상황을 말해보자면, Service 단에서 학원의 모든 수강신청내역을 가져오는 findAllEnrollmentForPay
메서드가 있었다.
그리고, 해당 메서드는 미결제 상태인 수강 신청 내역을 신청 최신순으로 조회하는 findAllByAcademyIdAndPaymentYNIsFalseOrderByCreatedAtDesc
메서드를 이용해서 수강 db에서 데이터를 찾아오고 있었다.
해당 메서드는 다음과 같다.
public Page<FindEnrollmentResponse> findAllEnrollmentForPay(Long academyId, Pageable pageable) {
//해당 학원의 모든 수강 신청 내역을 page 로 가져온다.
Page<Enrollment> foundAllEnrollments = enrollmentRepository.findAllByAcademyIdAndPaymentYNIsFalseOrderByCreatedAtDesc(academyId, pageable);
return foundAllEnrollments.map(FindEnrollmentResponse::new);
}
그래서, 해당 메서드의 성공 테스트를 위해 다음과 같은 테스트코드를 작성했다.
lecture2
에 대해 student
와 student2
가 수강신청하도록 설정해두고, 해당 수강신청내역을 반환해서 제대로 조회가 되는지를 확인하는 상황이다.
@Test
@DisplayName("해당 학원의 미결제 상태의 모든 수강신청내역 조회 성공")
void findAllEnrollment_ForPay_success() {
Lecture lecture2 = Lecture.builder().id(2L).name("lecture2").price(10000).employee(teacher).maximumCapacity(10).currentEnrollmentNumber(0).build();
Enrollment enrollment3 = Enrollment.builder().id(3L).student(student).lecture(lecture2).paymentYN(false).build();
Enrollment enrollment4 = Enrollment.builder().id(4L).student(student2).lecture(lecture2).paymentYN(false).build();
PageImpl<Enrollment> enrollmentList = new PageImpl<>(List.of(enrollment3, enrollment4));
given(enrollmentRepository.findAllByAcademyIdAndPaymentYNIsFalseOrderByCreatedAtDesc(academy.getId(), pageable)).willReturn(enrollmentList);
Page<FindEnrollmentResponse> response = enrollmentService.findAllEnrollmentForPay(academy.getId(), pageable);
assertThat(response.getTotalPages()).isEqualTo(1L);
assertThat(response.getTotalElements()).isEqualTo(2);
then(enrollmentRepository).should(times(1)).findAllByAcademyIdAndPaymentYNIsFalseOrderByCreatedAtDesc(academy.getId(), pageable);
}
그리고, 해당 테스트 코드를 실행했더니…? 다음과 같이 NPE가 발생했다.
원인을 찾아가보니, FindEnrollmentResponse
의 DTO 클래스에서 Entity -> DTO 변환할 때 createdAt 값이 필요한데 넣어주지 않아서 NPE가 발생했던 것이다.
그런데, createdAt
은 부모클래스인 BaseEntity
에 존재하기 때문에 값을 넣어줄 수 없었다.
해결하는 제일 간단한 방법은 Enrollment
클래스에 @Setter
를 열어두고, 테스트코드 작성 시에 setCreatedAt
메서드로 값을 넣어주면 된다.
하지만, 테스트코드 작성을 위해 Setter
를 열어두는 것은 좋은 방법이 아니기에 다른 방법을 찾아야했다.
이제부터 그 방법을 설명하고자 한다.
1. ReflectionTestUtils 사용
ReflectionTestUtils
를 보면 public이 아닌 private 메소드에 사용할 수 있고 상속관계에 있는 메서드에도 사용할 수 있다.
setField
메서드를 이용해 값을 넣어주면 된다.
이렇게 진행하면, 컴파일 시점엔 접근할 수 없는 필드들을 런타임 시에 값을 넣어주게 된다.
값을 넣어줄 때 넘겨주어야 할 매개변수 순서는 다음과 같다.
- 상속하는 자식 클래스 객체
- 상속되는 부모 클래스(
.class
) - Reflection할 필드 이름
- 해당 필드에 넣어줄 값
- 해당 필드의 타입
ReflectionTestUtils.setField(enrollment3, BaseEntity.class, "createdAt", LocalDateTime.of(2021, 12, 6, 12, 0), LocalDateTime.class);
ReflectionTestUtils.setField(enrollment4, BaseEntity.class, "createdAt", LocalDateTime.of(2021, 12, 6, 13, 0), LocalDateTime.class);
이렇게 설정하고, 테스트를 다시 실행하면 성공적으로 실행된다.
그리고, 런타임 시에 우리가 ReflectionTestUtils
를 통해 넣어준 값들이 들어갔다는 것을 볼 수 있다.
Leave a comment