TIL

내일배움캠프 TIL 25.

phonebee 2025. 2. 26. 16:44

▶ CH6 Spring 심화 개인 과제

▷ Level 3 테스트 코드 연습

● 1. 테스트 코드 연습 - 1

@ExtendWith(SpringExtension.class)
class PasswordEncoderTest {

    @InjectMocks
    private PasswordEncoder passwordEncoder;

    @Test
    void matches_메서드가_정상적으로_동작한다() {
        // given
        String rawPassword = "testPassword";
        String encodedPassword = passwordEncoder.encode(rawPassword);

        // when
        boolean matches = passwordEncoder.matches(encodedPassword, rawPassword);

        // then
        assertTrue(matches);
    }
}

해당 테스트 코드가 의도대로 성공할 수 있도록 수정하는 것이 이번 문제의 목표이다.

 

이 테스트 코드는 PasswordEncoder 내의 matches메서드가 정상적으로 작동을 하는지 검증을 하는 테스트이다.

@Component
public class PasswordEncoder {

    public String encode(String rawPassword) {
        return BCrypt.withDefaults().hashToString(BCrypt.MIN_COST, rawPassword.toCharArray());
    }

    public boolean matches(String rawPassword, String encodedPassword) {
        BCrypt.Result result = BCrypt.verifyer().verify(rawPassword.toCharArray(), encodedPassword);
        return result.verified;
    }
}

matches메서드는 이런 구조를 가지고 있다.

 

우선 현재 잘못 작성된 테스트 코드를 실행하면

잘못 작성된 테스트 코드 실행 결과

오류가 나타난다.

이 오류는 matches의 값이 true가 되야하는데 false 값을 가져와서 생긴 오류이다.

그럼 왜 false 값을 가져오게 되는 걸까?

 

boolean matches = passwordEncoder.matches(encodedPassword, rawPassword);

문제는 여기에 있다.

matches 메서드는

public boolean matches(String rawPassword, String encodedPassword) {
    BCrypt.Result result = BCrypt.verifyer().verify(rawPassword.toCharArray(), encodedPassword);
    return result.verified;
}

rawPassword와 encodedPassword 두 가지 매개변수를 받아서 실행한다.

잘못 작성된 코드에서는 rawPassword 값에 encodedPassword를 encodedPassword 값에는 rawPassword를 주입하여 matches메서드를 실행시키고 있다.

이렇게 되면 결과값이 일치하지 않게 되어 false가 반환된다.

 

그렇기에 의도대로 성공할 수 있게 수정하면

@Test
void matches_메서드가_정상적으로_동작한다() {
    // given
    String rawPassword = "testPassword";
    String encodedPassword = passwordEncoder.encode(rawPassword);

    // when
    boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);

    // then
    assertTrue(matches);
}

이렇게 매개변수 값을 올바르게 주입해야한다.

 

● 테스트 코드 연습 - 2

○ 1번 케이스

@Test
public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() {
    // given
    long todoId = 1L;
    given(todoRepository.findById(todoId)).willReturn(Optional.empty());

    // when & then
    InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId));
    assertEquals("Manager not found", exception.getMessage());
}

해당 테스트가 성공하고 컨텍스트와 일치하도록 테스트 코드와 테스트 코드 메서드 명을 수정하는 것이 이번 문제의 목표이다.

 

이 테스트 코드는 ManagerService 내의 getManagers메서드를 검증하는 코드이다.

여기서 getManagers메서드는

@Transactional(readOnly = true)
public List<ManagerResponse> getManagers(long todoId) {
    Todo todo = todoRepository.findById(todoId)
            .orElseThrow(() -> new InvalidRequestException("Todo not found"));

    List<Manager> managerList = managerRepository.findByTodoIdWithUser(todo.getId());

    List<ManagerResponse> dtoList = new ArrayList<>();
    for (Manager manager : managerList) {
        User user = manager.getUser();
        dtoList.add(new ManagerResponse(
                manager.getId(),
                new UserResponse(user.getId(), user.getEmail())
        ));
    }
    return dtoList;
}

이런 구조를 가진다.

 

우선, 현재 잘못 작성된 테스트 코드를 실행하면

잘못 작성된 테스트 코드 실행 오류

해당 오류가 나타난다.

이 오류는 "Manager not found"가 아닌 "Todo not found"를 반환 받아서 생긴 오류이다.

그럼 해당 오류를 해결하기 위해서는 어떻게 해야할까?

 

asserEquals에서 비교하는 값을 "Todo not found"로 바꾸면 된다.

@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
    // given
    long todoId = 1;
    CommentSaveRequest request = new CommentSaveRequest("contents");
    AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);

    given(todoRepository.findById(anyLong())).willReturn(Optional.empty());

    // when
    InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> {
        commentService.saveComment(authUser, todoId, request);
    });

    // then
    assertEquals("Todo not found", exception.getMessage());
}

변경한 후 다시 테스트 코드를 작동하면 정상적으로 테스트를 완료하게 된다.

 

○ 2번 케이스

@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
    // given
    long todoId = 1;
    CommentSaveRequest request = new CommentSaveRequest("contents");
    AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);

    given(todoRepository.findById(anyLong())).willReturn(Optional.empty());

    // when
    ServerException exception = assertThrows(ServerException.class, () -> {
        commentService.saveComment(authUser, todoId, request);
    });

    // then
    assertEquals("Todo not found", exception.getMessage());
}

해당 테스트가 성공할 수 있도록 테스트 코드를 수정하는 것이 이번 문제의 목표이다.

 

이 테스트 코드는 CommentService의 saveComment메서드를 검증하는 코드이다

 

saveComment메서드는

@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"));

    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())
    );
}

이런 구조를 가진다.

 

우선, 현재 잘못 작성된 테스트 코드를 실행하면

잘못된 테스트 코드 실행 시 나타나는 오류

오류가 나타난다

해당 오류는 InvalidRequestException이 나타나는데 ServerException으로 예외처리를 하여 나타나는 문제이다.

그럼 해당 에러를 고치기 위해서는 뭘해야할까?

 

바로 ServerException으로 던지는 것을 InvalidRequestException으로 변경해야한다.

@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
    // given
    long todoId = 1;
    CommentSaveRequest request = new CommentSaveRequest("contents");
    AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);

    given(todoRepository.findById(anyLong())).willReturn(Optional.empty());

    // when
    InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> {
        commentService.saveComment(authUser, todoId, request);
    });

    // then
    assertEquals("Todo not found", exception.getMessage());
}

그래서 테스트 코드를 수정한 후 다시 테스트를 실행하게되면 성공적으로 테스트를 완료하게 된다.

 

○ 3번 케이스

@Test
void todo의_user가_null인_경우_예외가_발생한다() {
    // given
    AuthUser authUser = new AuthUser(1L, "a@a.com", UserRole.USER);
    long todoId = 1L;
    long managerUserId = 2L;

    Todo todo = new Todo();
    ReflectionTestUtils.setField(todo, "user", null);

    ManagerSaveRequest managerSaveRequest = new ManagerSaveRequest(managerUserId);

    given(todoRepository.findById(todoId)).willReturn(Optional.of(todo));

    // when & then
    InvalidRequestException exception = assertThrows(InvalidRequestException.class, () ->
        managerService.saveManager(authUser, todoId, managerSaveRequest)
    );

    assertEquals("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.", exception.getMessage());
}

해당 테스트 코드가 성공할 수 있도록 서비스 로직을 수정하는 것이 이번 문제의 목표이다.

 

이 테스트가 검증하고 있는 서비스 로직은

@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
    // 일정을 만든 유저
    User user = User.fromAuthUser(authUser);
    Todo todo = todoRepository.findById(todoId)
            .orElseThrow(() -> new InvalidRequestException("Todo not found"));

    if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
        throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
    }

    User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
            .orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));

    if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
        throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
    }

    Manager newManagerUser = new Manager(managerUser, todo);
    Manager savedManagerUser = managerRepository.save(newManagerUser);

    return new ManagerSaveResponse(
            savedManagerUser.getId(),
            new UserResponse(managerUser.getId(), managerUser.getEmail())
    );
}

이런 구조를 가진다.

 

우선 잘못 작성된 테스트 코드를 실행하면

잘못 작성된 테스트 코드를 실행한 결과

해당 오류가 나타나는 원인은 해당 테스트 코드는 todo의 user가 null인 경우를 검증하려고 하지만, 실제 서비스 코드에서 todo.getUser().getId()를 호출하는 부분에서 NullPointerException(NPE)이 발생하게 된다.

 

즉, InvalidRequestException을 기대하고 있지만 NullPointerException이 발생하여 테스트가 실패한 것이다.

 

이를 해결하려면 todo.getUser()가 null일 경우를 체크하여 InvalidRequestException을 던지는 코드를 작성해야한다.

@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
    // 일정을 만든 유저
    User user = User.fromAuthUser(authUser);
    Todo todo = todoRepository.findById(todoId)
            .orElseThrow(() -> new InvalidRequestException("Todo not found"));

    // todo.getUser()가 null일 경우를 체크
    if(todo.getUser()==null){
        throw new InvalidRequestException("일정을 만든 유저 정보가 존재하지 않습니다.");
    }

    if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
        throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
    }

    User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
            .orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));

    if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
        throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
    }

    Manager newManagerUser = new Manager(managerUser, todo);
    Manager savedManagerUser = managerRepository.save(newManagerUser);

    return new ManagerSaveResponse(
            savedManagerUser.getId(),
            new UserResponse(managerUser.getId(), managerUser.getEmail())
    );
}

위 코드에서 

// todo.getUser()가 null일 경우를 체크
if(todo.getUser()==null){
    throw new InvalidRequestException("일정을 만든 유저 정보가 존재하지 않습니다.");
}

를 작성하여 InvalidException을 던지도록 코드를 작성한 후 테스트를 시작하면 정상적으로 테스트가 완료된다.

'TIL' 카테고리의 다른 글

플러스 주차 개인과제 TIL 1.  (0) 2025.03.10
Spring 심화주차 개인 과제 TIL  (0) 2025.02.27
내일배움캠프 TIL 24.  (0) 2025.02.24
내일배움캠프 TIL 23.  (0) 2025.02.21
뉴스피드 프로젝트 트러블 슈팅 TIL  (0) 2025.02.20