인프런 커뮤니티 질문&답변

han님의 프로필 이미지

작성한 질문수

Practical Testing: 실용적인 테스트 가이드

Business Layer 테스트 (3)

테스트할 때 Auditing 없이 원하는 날짜를 설정할 수 잇을까요?

작성

·

174

·

수정됨

0

공부삼아서 과제 프로젝트에 테스트를 도입하다가 궁금한 게 잇어서 질문드립니다.  

ATM_240530_175236.jpg

거래내역 조회를 구현할 때 2024-05-30 같은 거래가 발생할 날짜는 auditing, @CreatedAt 통해 저장된 createdAt에서 가져오도록 구현했습니다. (디자인과 달리, 최근 거래일수록 위로 오도록 변경되었습니다) 구현이 끝난 뒤, 테스트할 때 createAt을 임의로 변경하기 위해 BaseEntity의 private를 protected, 그리고 createdAt을 변경하는 메서드를 추가햇습니다. (이게 맞는 접근인지는 잘 모르겟네요... 할 때에도 이렇게 해도 되나? 라는 의문이 계속 들엇습니다...)

@DisplayName("유저의 계좌 내역을 불러올 수 있다.")
    @Test
    void getUserStatements() {
        // given (6/5 18:00)
        LocalDateTime now = LocalDateTime.of(2024, 6, 5, 18, 0, 0);

        User u1 = User.of("test1", "id1", "pw");
        User u2 = User.of("test2", "id2", "pw");
        List<User> users = userRepository.saveAll(List.of(u1, u2));
        User loginUser = users.get(0);
        User notLoginUser = users.get(1);

        Account a1 = Account.of("111-222-3333", 1000, loginUser);
        Account a2 = Account.of("111-222-3334", 2000, notLoginUser);
        List<Account> accounts = accountRepository.saveAll(List.of(a1, a2));
        Account loginUserAccount = accounts.get(0);
        Account notLoginUserAccount = accounts.get(1);

        // 자신 계좌로 입금 (조회 대상), 6/5 발생
        Statement s1 = createStatementForTest(loginUserAccount, loginUserAccount, 100, DEPOSIT);

        // 자신 계좌에서 출금 (조회 대상), 6/6 발생
        Statement s2 = createStatementForTest(loginUserAccount, loginUserAccount, 200, WITHDRAW);

        // 자신 계좌에서 타 계좌로 이체 (조회 대상), 6/7 발생
        Statement s3 = createStatementForTest(loginUserAccount, notLoginUserAccount, 300, TRANSFER);

        // 타 계좌에서 자신 계좌로 이체 (조회 대상), 6/8 발생
        Statement s4 = createStatementForTest(notLoginUserAccount, loginUserAccount, 400, TRANSFER);

        // 타 계좌에서 타 계좌로 출금 (조회 대상 X), 6/9 발생
        Statement s5 = createStatementForTest(notLoginUserAccount, notLoginUserAccount, 50000, WITHDRAW);
        statementRepository.saveAll(List.of(s1, s2, s3, s4, s5));

        // when
        StatementResponse userStatement = statementService.getUserStatements(loginUser);

        // then
        assertThat(userStatement.getStatementInfos()).as("거래 이력을 조회할 수 있다.").hasSize(4)
                .extracting("amount", "type")
                .containsExactly(
                        tuple(400, "이체"),
                        tuple(-300, "이체"),
                        tuple(-200, "출금"),
                        tuple(100, "입금")
                );

        assertThat(userStatement.getBalance()).as("계좌 잔액을 조회할 수 있다.").isEqualTo(1000);
    }

(위 테스트 코드는 시간 설정 및 검증 코드를 제거한 테스트입니다.) 위 메서드와 plusDays() 통해 시간 설정 후, extracting에 "date" (createdAt에 yyyy-MM-dd를 적용시켜 String 형태로 저장) 를 추가해서 확인했을 때에는 의도한 대로 2024-06-08, 2024-06-07, 2024-06-06, 2024-06-05가 아닌, 전부 테스트를 실행한 시간으로(2024-06-05) 나오는 걸 확인했습니다. 일단은 date를 검증하는 로직을 빼고 테스트를 통과시켰고, postman에서 입금/출금/이체 등 실시 후 조회했을 때에도 의도한 대로 최근 거래가 위로 오도록, 날짜 형식도 의도한 대로 나온 것도 볼 수 있었습니다.

하지만 이 date를 postman이 아닌, auditing 없이 임의로 날짜를 설정한 후 테스트해 확인해보고 싶은데, 이 경우에는 어떻게 하면 될까요? auditing 잇는 상태로도 가능하다면 알려주시면 감사하겟습니다!

 

아래는 BaseEntity와 거래 내역을 저장하는 Statement Entity 코드입니다

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity{
   @CreatedDate
   private LocalDateTime createdDate;

}
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;

import static jakarta.persistence.EnumType.*;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Statement extends BaseEntity{

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="from_account")
    private Account from;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="to_account")
    private Account to;

    private int amount;

    @Enumerated(STRING)
    private TransactionType type;

    @Builder
    private Statement(Account from, Account to, int amount, transactionType type) {
        this.from = from;
        this.to = to;
        this.amount = amount;
        this.type = type;
    }

    public static Statement of(Account from, Account to, int amount, transactionType type){
        return Statement.builder()
                .from(from)
                .to(to)
                .amount(amount)
                .type(type)
                .build();
    }


}

 

긴 글 읽어주셔서 감사합니다~

답변 2

0

박우빈님의 프로필 이미지
박우빈
지식공유자

안녕하세요, han 님!

2가지 방법이 있을 것 같네요 ㅎㅎ

  • ReflectionTestUtils를 사용하여 특정 필드의 값을 내가 원하는 형태로 조작할 수 있습니다. 다만 질문주신 경우처럼 꼭 필요한 상황에서만 사용하는 것이 좋습니다.

  • 애초에, '거래일자'와 같이 도메인에서 아주 중요한 필드라면, createdAt 대신 별도의 컬럼으로 관리하는 것이 좋습니다. Auditing 기능으로 자동 생성되는 데이터들은, 비즈니스 레이어에서 제어할 수 없는 값이기 때문에 개발 참고용으로 활용하거나, 중요도가 낮은 케이스에 활용하는 것이 좋아요. 거래일자를 별도 컬럼으로 관리한다면 보다 테스트하기 용이한 구조를 가져갈 수 있겠죠 ㅎㅎ

도움이 되셨기를 바랍니다.
감사합니다. 🙂

han님의 프로필 이미지
han
질문자

감사합니다~

0

안녕하세요, 인프런 AI 인턴입니다.

han님의 질문에 대해서는 “시간대에 따라 다른 결과를 주는 로직을 테스트하는 방법”과 매우 유사한 내용이 이전에도 논의된 적이 있어, 그 해결 방안을 참고하셔도 좋을 것 같습니다.

여기서 주요 포인트는 LocalDateTime이나 관련 시간을 다루는 객체를 테스트 로직에서 직접 제어할 수 없을 때, 외부에서 시간 정보를 주입받아 이를 기반으로 행동을 제어하는 방식입니다. 이 방식은 시간을 포함한 외부 의존성이 있는 로직을 테스트할 때 자주 사용됩니다.

관련 질문 링크:

핵심적인 내용으로는:

  1. LocalDateTime 등의 현재 시간을 바로 사용하지 않고, 시간 정보를 제공하는 별도의 클래스(TimeProvider)를 사용하는 방법입니다.
  2. 해당 클래스에서는 현재 시간을 반환하는 메서드를 정의하고, 테스트 시에는 이 메서드가 특정 시간을 반환하도록 모킹(mocking)합니다.
  3. 이로써 실제 코드에서는 변경 없이 테스트 코드 상에서만 시간을 조정할 수 있게 됩니다.

이 방법을 사용하면 @CreatedDate 등 JPA Auditing을 사용하여 자동으로 설정되는 시간 필드의 값을 테스트에서 임의로 조정할 수 있는 환경을 만들 수 있습니다. 특히 JPA Auditing 필드가 private으로 설정되어 있어 직접 값을 변경할 수 없는 경우에 유용하게 활용될 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

han님의 프로필 이미지

작성한 질문수

질문하기