테스트 때문에 엔티티에 setter를 추가하면 안 되는 이유
setTempHoldExpiresAt()을 둘러싼 선택
테스트를 돌리려고 엔티티에 setTempHoldExpiresAt()을 추가하는 건 권하지 않는다. 만료 시각을 조작할 일이 있다면 Repository 레벨에서 테스트 전용으로 열어두는 편이 낫다. 지금 구조에서는 setter가 없는 쪽이 오히려 설계상 옳은 상태다.
왜 setTempHoldExpiresAt()이 없는 게 맞는가
Reservation은 상태 전이 규칙을 가진 도메인이다
Reservation은 단순한 데이터 객체가 아니라 명확한 상태 전이 규칙(State Transition)을 가진 도메인 엔티티다. 예를 들어 TEMP_HOLD → CONFIRMED, TEMP_HOLD → EXPIRED 같은 전이가 있다.
여기서 중요한 건 tempHoldExpiresAt이 상태 전이의 결과라는 점이다. 외부에서 마음대로 수정하는 값이 아니다. 만료 시각은 임시 배정 상태에 들어갈 때 계산되어 설정되는 값이고, 그 자체가 도메인 규칙의 일부다.
만약 엔티티에 이런 setter가 생긴다면:
1
reservation.setTempHoldExpiresAt(...)
상태 전이를 거치지 않고 도메인 규칙을 우회해서 내부 상태를 직접 조작하는 통로를 여는 셈이다. 그렇게 되면 도메인 캡슐화가 깨진다.
테스트 편의를 위해 도메인을 훼손하면 안 된다
테스트 때문에 setter를 추가하는 건 결국 도메인 캡슐화를 포기하고, 상태 전이 규칙을 무력화하고, 설계 안정성보다 테스트 편의성을 앞세우는 선택이다. 좋은 테스트라고 보기 어렵다. 잘 만든 테스트는 대상 설계를 바꾸라고 요구하지 않는다.
문제는 여기서 끝나지 않는다. 이 setter는 실서비스 코드에도 그대로 남고, 누군가 실수로든 급한 수정으로든 호출해버릴 수 있다. 그러면 도메인의 불변 조건이 무너진다. 테스트를 위해 넣은 코드가 운영 리스크로 돌아오는 전형적인 패턴이다.
정석적인 해결: Repository 레벨의 테스트 전용 조작
도메인에는 손대지 않고 영속성 계층에서만 테스트용 조작을 허용하는 방식이다. 예를 들면 이렇게 된다.
1
2
3
4
5
6
7
8
public interface ReservationRepositoryPort {
void forceExpire(
UUID reservationId,
LocalDateTime expiredAt
);
}
이 메서드는 테스트 전용이고, 상태 전이 규칙을 대체하지 않으며, 단순히 DB 상태를 조작해서 시간 경과를 시뮬레이션할 뿐이다. 그래서 엔티티 불변성과 도메인 규칙을 지키면서도 테스트에서만 시간을 조작할 수 있고, 실서비스 로직과도 깔끔하게 분리된다. 도메인은 여전히 자기 규칙대로만 상태가 바뀐다.
왜 Repository 레벨이 적절한가
책임 분리 때문이다. 도메인은 상태 전이 규칙을 지키고 외부 조작을 허용하지 않는다. Repository는 상태를 어떻게 저장하고 조회할지 책임지며, 테스트에서는 현실적으로 필요한 “시간 이동”을 시뮬레이션한다.
테스트에서 시간 조작이 필요한 건 현실이다. 하지만 그 책임을 도메인에 넘기면 설계가 오염된다. 규칙을 지키는 일은 도메인에, 현실을 흉내 내는 일은 Repository에 두는 역할 분리가 그래서 중요하다.
정리
setTempHoldExpiresAt()이 없는 건 설계상 정상이고, 테스트 때문에 엔티티에 setter를 추가하는 건 안티 패턴이다. 테스트 전용 상태 조작은 Repository 레벨에서 푸는 게 정석이다. 이렇게 하면 도메인은 깨끗하게 유지되고, 테스트는 현실적으로 돌아가며, 구조는 오래 버틴다. 테스트를 위해 도메인을 망가뜨리지 않는 것, 그게 결국 가장 오래가는 선택이다.