@Transactional
public CommentSaveResponse saveComment(AuthUser authUser, long todoId, CommentSaveRequest commentSaveRequest) {
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId).orElseThrow(() ->
new InvalidRequestException("Todo not found"));
List<User> userList = managerRepository.findByTodoId(todoId);
if(!userList.contains(user)) {
throw new InvalidRequestException("담당자만 댓글을 달 수 있습니다.");
}
Comment newComment = new Comment(
commentSaveRequest.getContents(),
user,
todo
);
Comment savedComment = commentRepository.save(newComment);
return new CommentSaveResponse(
savedComment.getId(),
savedComment.getContents(),
new UserResponse(user.getId(), user.getEmail())
);
}
@Test
public void comment를_정상적으로_등록한다() {
// given
long todoId = 1L;
CommentSaveRequest request = new CommentSaveRequest("contents");
AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);
User user = User.fromAuthUser(authUser);
Todo todo = new Todo("title", "title", "contents", user);
Comment comment = new Comment(request.getContents(), user, todo);
given(todoRepository.findById(todoId)).willReturn(Optional.of(todo));
given(managerRepository.findByTodoId(todoId)).willReturn(List.of(user));
given(commentRepository.save(any())).willReturn(comment);
// when
CommentSaveResponse result = commentService.saveComment(authUser, todoId, request);
// then
assertNotNull(result);
}
로직상 테스트에서 예외가 일어날 부분이 없는데, 자꾸 List.of(user)를 인식하지 못하는것 같아 답답했다. 튜터님도 바빠보이시고 해서 혹시나 하는 마음에 GPT한테 물어봤는데, 똑똑하다 이자식..
다음은 해결을 제시해준 GPT의 전문이다.
에러 메시지 "담당자만 댓글을 달 수 있습니다."가 발생하는 이유는 테스트 코드에서 설정한 userList
와 실제 user
가 일치하지 않아서일 수 있습니다. Spring 테스트를 작성할 때, Mockito
를 사용할 때 주의할 점이 몇 가지 있습니다.
userList
설정 문제: managerRepository.findByTodoId(anyLong())
를 List.of(user)
로 설정했는데, 실제 userList
와 비교할 때, user
객체가 동일하지 않거나 잘못 설정되어 있을 수 있습니다.
객체 동등성 문제: List.of(user)
는 동일한 인스턴스의 user
를 반환하지만, 실제로는 User
객체의 동등성 비교가 예상과 다를 수 있습니다. User
클래스가 equals
및 hashCode
메서드를 제대로 구현하지 않으면 문제가 발생할 수 있습니다.
equals
및 hashCode
구현 확인: User
클래스에서 equals
와 hashCode
메서드가 올바르게 구현되어 있는지 확인하세요. 객체 동등성을 비교할 때, 메모리 주소가 아니라 객체의 실제 값이 비교되어야 합니다.equals
와 hashCode
메서드의 역할과 이들이 왜 중요한지 쉽게 설명해드릴게요.
equals
와 hashCode
메서드의 역할equals
메서드목적: 두 객체가 "동일한지" 비교하는 역할을 합니다.
기본 동작: Object
클래스에서 기본 equals
메서드는 두 객체의 메모리 주소를 비교합니다. 즉, 같은 객체 인스턴스인지 여부를 판단합니다.
재정의: 많은 경우, 객체의 내용(속성)으로 비교하는 것이 필요합니다. 예를 들어, 두 개의 User
객체가 같은 id
와 email
을 가지고 있다면 같은 사용자로 간주하고 싶을 수 있습니다. 이럴 때 equals
메서드를 재정의하여 두 객체의 특정 속성을 비교하도록 설정합니다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(email, user.email);
}
hashCode
메서드목적: 객체의 해시값을 반환합니다. 주로 해시 기반 컬렉션(예: HashMap
, HashSet
)에서 사용됩니다.
기본 동작: Object
클래스의 기본 hashCode
메서드는 객체의 메모리 주소를 기반으로 해시값을 생성합니다.
재정의: equals
메서드를 재정의할 때, hashCode
도 함께 재정의해야 합니다. 동일한 객체는 동일한 해시값을 가져야 하기 때문입니다.
@Override
public int hashCode() {
return Objects.hash(id, email);
}
테스트에서 userList.contains(user)
를 호출할 때, userList
에는 user
와 같은 속성을 가진 다른 User
객체가 들어 있었습니다. 기본적으로 List
의 contains
메서드는 객체의 equals
메서드를 사용하여 객체의 동등성을 확인합니다.
equals
메서드는 true
를 반환합니다.equals
가 재정의되지 않으면, List
에서 객체를 찾을 때 메모리 주소를 비교합니다. 재정의된 equals
를 사용하면, 객체의 속성 값으로 비교합니다.기본 equals
와 hashCode
:
User user1 = new User(1L, "email");
User user2 = new User(1L, "email");
System.out.println(user1.equals(user2)); // false, 기본 equals는 메모리 주소를 비교
재정의된 equals
와 hashCode
:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, email);
}
User user1 = new User(1L, "email");
User user2 = new User(1L, "email");
System.out.println(user1.equals(user2)); // true, id와 email이 같으므로
equals
: 두 객체가 같은 속성 값을 가지는지 비교합니다.hashCode
: 객체의 해시값을 반환하며, 같은 객체는 같은 해시값을 가져야 합니다.이 두 메서드를 제대로 구현하지 않으면, 컬렉션(예: List
, Set
)에서 객체를 찾거나 비교할 때 예상치 못한 동작이 발생할 수 있습니다. 이로 인해 테스트 코드에서 원하는 결과를 얻지 못할 수 있습니다.
따라서 User
객체의 동등성을 정의할 때, equals
와 hashCode
를 올바르게 구현하여, 객체의 내용으로 비교하도록 하는 것이 중요합니다.