240912 내일배움캠프 백엔드 Java 6기 TroubleShooting : 객체의 동일성 비교하기

박대현·2024년 9월 12일
0
@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를 사용할 때 주의할 점이 몇 가지 있습니다.

문제 원인

  1. userList 설정 문제: managerRepository.findByTodoId(anyLong())List.of(user)로 설정했는데, 실제 userList와 비교할 때, user 객체가 동일하지 않거나 잘못 설정되어 있을 수 있습니다.

  2. 객체 동등성 문제: List.of(user)는 동일한 인스턴스의 user를 반환하지만, 실제로는 User 객체의 동등성 비교가 예상과 다를 수 있습니다. User 클래스가 equalshashCode 메서드를 제대로 구현하지 않으면 문제가 발생할 수 있습니다.

해결 방법

  1. equalshashCode 구현 확인: User 클래스에서 equalshashCode 메서드가 올바르게 구현되어 있는지 확인하세요. 객체 동등성을 비교할 때, 메모리 주소가 아니라 객체의 실제 값이 비교되어야 합니다.

equalshashCode 메서드의 역할과 이들이 왜 중요한지 쉽게 설명해드릴게요.

equalshashCode 메서드의 역할

1. equals 메서드

  • 목적: 두 객체가 "동일한지" 비교하는 역할을 합니다.

  • 기본 동작: Object 클래스에서 기본 equals 메서드는 두 객체의 메모리 주소를 비교합니다. 즉, 같은 객체 인스턴스인지 여부를 판단합니다.

  • 재정의: 많은 경우, 객체의 내용(속성)으로 비교하는 것이 필요합니다. 예를 들어, 두 개의 User 객체가 같은 idemail을 가지고 있다면 같은 사용자로 간주하고 싶을 수 있습니다. 이럴 때 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);
    }

2. hashCode 메서드

  • 목적: 객체의 해시값을 반환합니다. 주로 해시 기반 컬렉션(예: HashMap, HashSet)에서 사용됩니다.

  • 기본 동작: Object 클래스의 기본 hashCode 메서드는 객체의 메모리 주소를 기반으로 해시값을 생성합니다.

  • 재정의: equals 메서드를 재정의할 때, hashCode도 함께 재정의해야 합니다. 동일한 객체는 동일한 해시값을 가져야 하기 때문입니다.

    @Override
    public int hashCode() {
        return Objects.hash(id, email);
    }

문제 발생 원인

객체 동등성 비교 문제

테스트에서 userList.contains(user)를 호출할 때, userList에는 user와 같은 속성을 가진 다른 User 객체가 들어 있었습니다. 기본적으로 Listcontains 메서드는 객체의 equals 메서드를 사용하여 객체의 동등성을 확인합니다.

  • 동일 객체 인스턴스: 객체 인스턴스가 같으면 equals 메서드는 true를 반환합니다.
  • 동일 속성: equals가 재정의되지 않으면, List에서 객체를 찾을 때 메모리 주소를 비교합니다. 재정의된 equals를 사용하면, 객체의 속성 값으로 비교합니다.

예시

  1. 기본 equalshashCode:

    User user1 = new User(1L, "email");
    User user2 = new User(1L, "email");
    
    System.out.println(user1.equals(user2)); // false, 기본 equals는 메모리 주소를 비교
  2. 재정의된 equalshashCode:

    @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 객체의 동등성을 정의할 때, equalshashCode를 올바르게 구현하여, 객체의 내용으로 비교하도록 하는 것이 중요합니다.

0개의 댓글