코드 그라데이션

230217 Service단 TestCode 전후 비교 본문

Spring/Test Code

230217 Service단 TestCode 전후 비교

완벽한 장면 2023. 2. 26. 00:07

전체 코드(한꺼번에 연달아서 붙여넣기)

컨트롤러

@RestController
@RequestMapping("/boards")
@RequiredArgsConstructor
public class BoardController {

  private final BoardService boardService;
  private final SetHttpHeaders httpHeaders;

  // 게시글 생성
  @PostMapping
  public ResponseEntity<StatusResponse> createBoard(
      @RequestBody BoardRequestDto boardRequestDto,
      @AuthenticationPrincipal UserDetailsImpl userDetails) {
    boardService.createBoard(boardRequestDto, userDetails.getUser());
    return ResponseEntity.status(HttpStatus.CREATED)
            .body(StatusResponse.valueOf(ResponseMessages.CREATED_SUCCESS));
  }

  // 게시글 수정
  @PutMapping("/{boardId}")
  public ResponseEntity<StatusResponse> updateBoard(@PathVariable Long boardId,
      @RequestBody BoardRequestDto boardRequestDto,
      @AuthenticationPrincipal UserDetailsImpl userDetails) {
    boardService.updateBoard(boardId, boardRequestDto, userDetails.getUser());
    return ResponseEntity.ok().body(StatusResponse.valueOf(ResponseMessages.SUCCESS));
  }

  // 게시글 단건 조회, 댓글 목록으로 불러오게 추가(페이징)
  @GetMapping("/{boardId}")
  public ResponseEntity<BoardResponseDto> getBoard(@PathVariable Long boardId) {
    return ResponseEntity.ok().headers(httpHeaders.setHeaderTypeJson())
        .body(boardService.getBoard(boardId));
  }

  // 게시글 전체 조회
  @GetMapping
  public ResponseEntity<Page<PagingBoardResponse>> getBoards(@RequestBody PageDto pageDto) {
    return ResponseEntity.ok().headers(httpHeaders.setHeaderTypeJson()).body(boardService.getBoards(pageDto));
  }

  // 게시물 삭제
  @DeleteMapping("{boardId}")
  public ResponseEntity<StatusResponse> deleteBoard(@PathVariable Long boardId,
      @AuthenticationPrincipal UserDetailsImpl userDetails) {
    boardService.deleteBoard(boardId, userDetails.getUser());
    return ResponseEntity.status(HttpStatus.NO_CONTENT).body(StatusResponse.valueOf(ResponseMessages.DELETE_SUCCESS));
  }
}

 

RequestDto

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BoardRequestDto {

  private String title;
  private String content;
  private BoardSubject subject;

}

 

ResponseDto

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BoardResponseDto {

  private String title;

  private String content;

  private BoardSubject subject;

  private List<CommentResponseDto> comments;

  private LocalDateTime createdAt;

  private LocalDateTime modifiedAt;

}

 

PagingBoardResponseDto

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class PagingBoardResponse {

  private String title;
  private BoardSubject subject;

  public PagingBoardResponse(Board board) {
    this.title = board.getTitle();
    this.subject = board.getSubject();
  }
}

 

Board

@Entity
@Getter
@NoArgsConstructor
public class Board extends TimeStamped {

  /**
   * 컬럼 - 연관관계 컬럼을 제외한 컬럼을 정의합니다.
   */
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String title;

  private String content;


  @Enumerated(EnumType.STRING)
  private BoardSubject subject;

  public enum BoardSubject {
    공지사항, 동네소식;
  }

  /**
   * 생성자 - 약속된 형태로만 생성가능하도록 합니다.
   */
  @Builder
  public Board(String title, String content, BoardSubject subject, User user) {
    this.title = title;
    this.content = content;
    this.subject = subject;
    this.user = user;
  }

  /**
   * 연관관계 - Foreign Key 값을 따로 컬럼으로 정의하지 않고 연관 관계로 정의합니다.
   */
  @ManyToOne(fetch = FetchType.LAZY)
  private User user;

  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true,fetch = FetchType.EAGER)
  private Set<Comment> comments = new LinkedHashSet<>();

  /**
   * 연관관계 편의 메소드 - 반대쪽에는 연관관계 편의 메소드가 없도록 주의합니다.
   */

  /**
   * 서비스 메소드 - 외부에서 엔티티를 수정할 메소드를 정의합니다. (단일 책임을 가지도록 주의합니다.)
   */
  public void update(BoardRequestDto boardRequestDto) {
    this.title = boardRequestDto.getTitle();
    this.content = boardRequestDto.getContent();
    this.subject = boardRequestDto.getSubject();
  }

  public boolean checkBoardWriter(User user) {
    return this.user.equals(user); // board가 가지고 있는 유저와 파라미터로 받은 유저를 비교해서 true false반환
  }

}

 

BoardRepository

@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {

  Page<Board> findAll(Pageable pageable);
}

 

BoardService(Interface)

public interface BoardService {

  Board createBoard(BoardRequestDto boardRequestDto, User user);

  void updateBoard(Long boardId, BoardRequestDto boardRequestDto, User user);

  BoardResponseDto getBoard(Long boardId);

  Page<PagingBoardResponse> getBoards(PageDto pageDto);

  void deleteBoard(Long boardId, User user);

  Board findBoardById(Long boardsId);
}

 

BoardServiceImpl

@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService {

  private final BoardRepository boardRepository;

  // 게시글 생성
  @Override
  @Transactional
  public Board createBoard(BoardRequestDto boardRequestDto, User user) {
    Board board = Board.builder()
        .title(boardRequestDto.getTitle())
        .content(boardRequestDto.getContent())
        .subject(boardRequestDto.getSubject())
        .user(user)
        .build();
    return boardRepository.save(board);
  }

  // 게시글 수정
  @Override
  @Transactional
  public void updateBoard(Long boardId, BoardRequestDto boardRequestDto, User user) {
    Board board = findBoardById(boardId);
    if (!board.checkBoardWriter(user)) {
      throw new IllegalArgumentException("본인 게시글이 아닙니다");
    }
    board.update(boardRequestDto);
  }

  // 게시글 전체 조회
  @Override
  @Transactional(readOnly = true)
  public Page<PagingBoardResponse> getBoards(PageDto pageDto) {
    Page<Board> boards = boardRepository.findAll(pageDto.toPageable());
    return boards.map(PagingBoardResponse::new);
  }

  // 게시글 단건 조회
  @Override
  @Transactional(readOnly = true)
  public BoardResponseDto getBoard(Long boardId) {
    Board board = findBoardById(boardId);
    return BoardResponseDto.builder()
        .title(board.getTitle())
        .content(board.getContent())
        .subject(board.getSubject())
        .comments(board.getComments().stream().map(CommentResponseDto::new).collect(Collectors.toList()))
        .createdAt(board.getCreatedAt())
        .modifiedAt(board.getModifiedAt())
        .build();
  }

  // 게시글 삭제
  @Override
  @Transactional
  public void deleteBoard(Long boardId, User user) {
    Board board = findBoardById(boardId);
    if(!board.checkBoardWriter(user)) {
      throw new IllegalArgumentException("본인의 게시글이 아닙니다.");
    }
    boardRepository.deleteById(board.getId());

  }
// 중복 로직 메서드 분리
  @Override
  @Transactional(readOnly = true)
  public Board findBoardById(Long boardId) {
    return boardRepository.findById(boardId).orElseThrow(() -> new IllegalArgumentException("게시글이 없습니다."));
  }
}

 

-----------------------------------------------------------------------------------------

 

작성한 테스트 코드 초안

@ExtendWith(MockitoExtension.class)
class BoardServiceImplTest {

  @Mock
  BoardRepository boardRepository;

  @InjectMocks
  BoardServiceImpl boardService;

  @Test
  @DisplayName("게시글 생성 성공 테스트")
  void createBoard() {
    BoardRequestDto requestDto = BoardRequestDto.builder()
        .title("title1")
        .content("content1")
        .build();

    User user = mock(User.class);

    //when
    boardService.createBoard(requestDto, user);

    //then
    verify(boardRepository).save(isA(Board.class));

  }

  @Test
  @DisplayName("게시글 업데이트 성공 테스트")
  void updateBoard() {
  
    // given
    User user = mock(User.class);
    Board board = mock(Board.class);
    BoardRequestDto boardRequestDto = mock(BoardRequestDto.class);

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));
    when(Optional.of(board).get().checkBoardWriter(user)).thenReturn(true);

    // when
    boardService.updateBoard(board.getId(), boardRequestDto, user);

    // then
    verify(board).update(boardRequestDto);
  }

  @Test
  @DisplayName("게시글 목록 불러오기 테스트")
  void getBoards() {
    // given
    Pageable pageable = mock(Pageable.class);
    PageDto pageDto = mock(PageDto.class);

    when(pageDto.toPageable()).thenReturn(pageable);
    when(boardRepository.findAll(pageable)).thenReturn(Page.empty());

    // when
    Page<PagingBoardResponse> pagingProductResponse = boardService.getBoards(pageDto);

    // then
    assertThat(pagingProductResponse).isNotNull();
  }

  @Test
  @DisplayName("게시글 단건 조회 테스트")
  void getBoard() {
    // given
    BoardRequestDto boardRequest = mock(BoardRequestDto.class);
    Board board = mock(Board.class);

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));

    // when
    BoardResponseDto boardResponse = boardService.getBoard(board.getId());

    // then
    assertThat(boardResponse.getContent()).isEqualTo(boardRequest.getContent());
  }

  @Test
  @DisplayName("게시글 삭제 성공 테스트")
  void deleteBoard() {

    //given
    Board board = mock(Board.class);
    User user = mock(User.class);

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));
    when(Optional.of(board).get().checkBoardWriter(user)).thenReturn(true);

    //when
    boardService.deleteBoard(board.getId(),user);

    //then
    verify(boardRepository).deleteById(board.getId());
    
    }
}

 

수정 후

@ExtendWith(MockitoExtension.class)
class BoardServiceImplTest {

  @Mock
  BoardRepository boardRepository;

  @InjectMocks
  BoardServiceImpl boardService;

  @Test
  @DisplayName("게시글 생성 성공 테스트")
  void createBoard() {
    BoardRequestDto requestDto = BoardRequestDto.builder()
        .title("title1")
        .content("content1")
        .subject(Board.BoardSubject.공지사항)
        .build();

    User user = mock(User.class);

    Board board = Board.builder()
            .title(requestDto.getTitle())
            .content(requestDto.getContent())
            .subject(Board.BoardSubject.공지사항)
            .build();

    given(boardRepository.save(any())).willReturn(board);

    //when
    Board savedBoard = boardService.createBoard(requestDto, user);

    // then
    assertThat(savedBoard.getTitle()).isEqualTo(requestDto.getTitle());
    assertThat(savedBoard.getContent()).isEqualTo(requestDto.getContent());

  }

  @Test
  @DisplayName("게시글 업데이트 성공 테스트")
  void updateBoard() {
  
    // given
    User user = mock(User.class);
    Board board = Board.builder()
            .title("test-title")
            .content("test-content")
            .subject(Board.BoardSubject.동네소식)
            .user(user)
            .build();

    BoardRequestDto boardRequestDto = BoardRequestDto.builder()
            .title("after-title")
            .content("after-content")
            .subject(Board.BoardSubject.공지사항)
            .build();

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));

    // when
    boardService.updateBoard(board.getId(), boardRequestDto, user);

    // then
    assertThat(board.getTitle()).isEqualTo(boardRequestDto.getTitle());
    assertThat(board.getContent()).isEqualTo(boardRequestDto.getContent());
    assertThat(board.getSubject()).isEqualTo(boardRequestDto.getSubject());

  }

  @Test
  @DisplayName("게시글 업데이트 실패 테스트: 본인 게시글이 아닌 경우")
  void failsUpdateBoard() {
  
    // given
    User userA = User.builder()
            .username("userA")
            .password("password1")
            .build();

    User userB = User.builder()
            .username("userB")
            .password("password2")
            .build();

    Board board = Board.builder()
            .title("test-title")
            .content("test-content")
            .subject(Board.BoardSubject.동네소식)
            .user(userA)
            .build();

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));

    // when
    assertThatThrownBy(() -> {

      boardService.updateBoard(board.getId(), null, userB);

    }).isInstanceOf(IllegalArgumentException.class);
  }

  @Test
  @DisplayName("게시글 목록 불러오기 테스트")
  void getBoards() {
    // given
    Pageable pageable = mock(Pageable.class);
    PageDto pageDto = mock(PageDto.class);

    when(pageDto.toPageable()).thenReturn(pageable);
    when(boardRepository.findAll(pageable)).thenReturn(Page.empty());

    // when
    Page<PagingBoardResponse> pagingProductResponse = boardService.getBoards(pageDto);

    // then
    assertThat(pagingProductResponse).isNotNull();
  }

  @Test
  @DisplayName("게시글 단건 조회 테스트")
  void getBoard() {

// given

    Board board = Board.builder()
            .title("test-title")
            .content("test-content")
            .build();

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));

    // when
    BoardResponseDto boardResponse = boardService.getBoard(board.getId());

    // then
    assertThat(boardResponse.getContent()).isEqualTo(board.getContent());
    assertThat(boardResponse.getTitle()).isEqualTo(board.getTitle());
  }

  @Test
  @DisplayName("게시글 삭제 성공 테스트")
  void deleteBoard() {
    //given
    Board board = mock(Board.class);
    User user = mock(User.class);

    when(boardRepository.findById(board.getId())).thenReturn(Optional.of(board));
    when(Optional.of(board).get().checkBoardWriter(user)).thenReturn(true);

    //when
    boardService.deleteBoard(board.getId(),user);

    //then
    verify(boardRepository, times(1)).deleteById(board.getId());
    
  }
}
728x90
Comments