단위 테스트 ID 설정하는 방법

ksngh·2025년 3월 9일

자바스프링

목록 보기
8/8

Spring Boot와 JPA를 활용한 프로젝트에서 단위 테스트를 진행할 때,
ID 값이 필요한 로직을 테스트하는 문제에 직면했다.

예를 들어, 게시판(Board)을 생성하는 기능을 테스트할 때,
게시판을 작성한 작성자(Member)의 ID가 정상적으로 저장되는지 확인해야 한다.

하지만 JPA에서는 ID 값이 영속성 컨텍스트(Persistence Context)를 통해 자동 생성되므로, 단위 테스트에서 ID를 강제로 설정하는 방법을 찾아야 했다.

이번 글에서는 다양한 방법의 장단점을 분석하고, 최종적으로 Reflection을 활용하는 방식을 선택한 이유를 정리해보겠다.

1. ID를 받는 생성자 추가

@Entity
@Getter
public class Member {

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

    public Member(Long id) { // ID를 직접 입력받는 생성자 추가
        this.id = id;
    }
}

✅ 장점

  • 간단하게 구현할 수 있다.
  • 별도의 추가 작업 없이 테스트에서 객체를 생성할 수 있다.

❌ 단점

  • 프로덕션 코드에서 잘못된 사용을 유발할 위험이 있다.
  • JPA에서 관리되지 않은 객체가 ID 값을 가지는 비정상적인 상태가 될 수 있다.
  • ID를 받는 생성자를 의도치 않게 사용할 가능성이 있다.

결론

  • 프로덕션 코드에 영향을 미치는 문제가 있어 제외했다.

2. 프록시 객체 생성

상속을 활용한 Proxy 객체 생성

public class MemberProxy extends Member {
    public MemberProxy(Long id) {
        super();
        this.setId(id); // protected setter 필요
    }
}

✅ 장점

  • 테스트에서만 사용할 수 있는 프록시 객체를 만들어 프로덕션 코드의 변경 없이 사용 가능

❌ 단점

  • 원본 엔티티(Member)에 protected setter를 추가해야 하는 문제
  • 프로덕션 코드에서 setId()를 사용할 가능성이 있음

결론

  • 프로덕션 코드에 영향을 주므로 제외

합성을 활용한 Proxy 객체 생성

public class MemberProxy {
    private final Long id;
    private final Member member;

    public MemberProxy(Long id) {
        this.id = id;
        this.member = new Member();
    }

    public Long getId() {
        return id;
    }
}

✅ 장점

  • 프로덕션 코드에 전혀 영향을 주지 않음
  • Member 클래스 자체는 변경되지 않음

❌ 단점

  • Member 타입이 아니기 때문에, 기존 로직에서 사용 불가능
  • boardService.addBoard(BoardCreateRequest request, Member member) 같은 메서드에서 타입 불일치로 오류 발생

결론

  • 타입 불일치로 인해 테스트에서 사용할 수 없어 제외

3. Mockito의 Spy 활용

class ParticipantTest {
    private Member member;

    @BeforeEach
    void setUp() {
        member = Mockito.spy(Fixture.member());
        when(member.getId()).thenReturn(1L);
    }
}

✅ 장점

  • spy()를 사용하여 ID 값을 원하는 대로 설정 가능
  • 기존 객체를 유지하면서 특정 메서드만 스터빙 가능
  • 별도의 프록시 클래스를 만들 필요 없음

❌ 단점

  • JPA 엔티티와의 호환성 문제 가능성
  • Spy 객체는 프록시 객체이므로, JPA 내부에서 예기치 않은 문제가 발생할 가능성이 있음
  • 결국에는, id가 있어야 board가 제대로 저장되고, boardService 안에서 member를 불러오기 때문에 id가 boardService안에서 제대로 주입되지 않음.

결론

  • Spy는 유용하지만 JPA 엔티티에 적용하는 것은 불안정하며, 서비스 내부에서 호출하는 로직에는 id를 주입할 수 없음

4. Reflection을 사용하여 ID 값 강제 설정

@BeforeEach
public void setUp() throws NoSuchFieldException, IllegalAccessException {
    savedMember = Fixture.member(); // ID 없이 생성

    // Reflection을 사용하여 ID 강제 설정
    Field idField = Member.class.getDeclaredField("id");
    idField.setAccessible(true);
    idField.set(savedMember, 1L);
}

✅ 장점

  • 프로덕션 코드에 영향을 주지 않음
  • 기존 객체를 그대로 유지하면서 ID 값만 설정 가능
  • JPA 엔티티와의 충돌 없이 정상 작동

❌ 단점

  • Reflection을 사용하여 접근 제어자를 우회하는 것이므로 코드 가독성이 다소 떨어질 수 있음
  • SecurityManager가 활성화된 환경에서는 사용할 수 없음

결론

  • 테스트에서만 사용하며, 프로덕션 코드에 영향을 주지 않으므로 최적의 선택!

정리

JPA 엔티티에서 ID 값이 필요한 단위 테스트를 진행하면서 여러 가지 방법을 시도했다.

제외한 방법들

  • ID를 받는 생성자 추가: 프로덕션 코드에 영향을 줌
  • 프록시 객체 생성: 별도 클래스를 만들거나, 타입 불일치 문제 발생
  • Mockito Spy 활용: JPA 엔티티와의 호환성 문제, 서비스 내부에 주입 불가

최종 선택: Reflection 활용

Reflection을 사용하여 ID 값을 강제로 설정하는 방식이 가장 적합하다고 판단했다.
이 방법은 프로덕션 코드에는 전혀 영향을 주지 않으면서, 테스트에서 ID 값을 설정할 수 있는 장점이 있다.

profile
백엔드 개발자입니다.

0개의 댓글