게시판 테스트에서 기능을 구현한다.
검색어가 없는경우와 있는 경우를 나누어 준다.
검색어가 없는 경우, 혹은 검색어가 공백인 경우는 articleRepository에서 pageable을 선택하고 해당 pageable을 dto로 변경한다. Page 내부의 여러 메소드 중에서 map이 있는데, 이는 Page의 내용물을 형변환해서 다시 페이지를 만들어주는 기능이다.
if(searchKeyword == null || searchKeyword.isBlank()) {
return articleRepository.findAll(pageable).map(ArticleDto::from);
}
반대로 검색어가 있다면, 도메인에서 SearchType으로 작성한 항목들마다 쿼리를 작성한다.
ArticleService.java
switch (searchType){
case TITLE -> articleRepository.findByTitleContaining(searchKeyword, pageable).map(ArticleDto::from);
case CONTENT -> articleRepository.findByContentContaining(searchKeyword, pageable).map(ArticleDto::from);
case ID -> articleRepository.findByUserAccount_UserIdContaining(searchKeyword, pageable).map(ArticleDto::from);
case NICKNAME -> articleRepository.findByUserAccount_NicknameContaining(searchKeyword, pageable).map(ArticleDto::from);
case HASHTAG -> articleRepository.findByHashtag(searchKeyword, pageable).map(ArticleDto::from);
}
키워드마다 각각의 findBy 메소드를 호출하는 것으로 해당 메소드들은 articleRepository에 작성해준다.
Page<Article> findByTitleContaining(String title, Pageable pageable);
Page<Article> findByContentContaining(String content, Pageable pageable);
Page<Article> findByUserAccount_UserIdContaining(String userId, Pageable pageable);
Page<Article> findByUserAccount_NicknameContaining(String nickname, Pageable pageable);
Page<Article> findByHashtag(String hashtag, Pageable pageable);
하지만 여기서 해시태그를 사용해서 검색을 한다면 매번 #을 입력해야해서 귀찮을 수 있기 때문에 미리 검색하기전에 #이 입력된 상태를 유지하게 해주면 더 좋을 것같다.
case HASHTAG -> articleRepository.findByHashtag("#" + searchKeyword, pageable).map(ArticleDto::from);
그럼 이제 searchArticles
메소드를 호출하는 테스트인 조회 테스트의 결과를 확인해보면
오케이 일단 2개의 테스트 모두 통과했다.
게시글을 조회할때 반환여부를 테스트할때 호출되는 메소드이다. 게시글의 id값을 단건 조회해주는 메소드를 작성한다. ArticleDto는 댓글에 대한 정보는 추가되어있지 않은 상태이며, 댓글과 게시글의 양방향으로 관계를 설정한 것에 대한 이득을 활용하지 못하고 있기 때문에 ArticleDto에 articleComment의 데이터를 추가한 articleWithCommentsDto를 만들어서 사용하고 있다.
@Transactional(readOnly = true)
public ArticleWithCommentsDto getArticle(Long articleId) {
return articleRepository.findById(articleId)
.map(ArticleWithCommentsDto::from)
.orElseThrow(() -> new EntityNotFoundException("게시글이 없습니다 - articleId:" + articleId));
}
이번 map의 경우는 Optional이기 때문에 오류가 발생할 경우를 작성해줘야 하므로 .orElseThrow()
를 작성한 것이다.
테스트를 돌려보면 이제 조회 테스트도 성공으로 나타난다.
게시글을 생성할때 호출하는 메서드이다. void 메서드이므로 리턴값은 없다.
public void saveArticle(ArticleDto dto) {
articleRepository.save(dto.toEntity());
}
ArticleDto에서 dto를 엔티티로 만드는 toEntity메서드를 작성했기 때문에 쉽게 코드 작성이 가능하다.
게시글을 수정할때 호출하는 메서드. 게시글의 content의 내용을 변경하고 다시 저장해주는 방식이다. 테스트에서는 articleRepository에서 getReferenceById를 호출해서 게시글의 내용을 가져오는 방식인데, 여기서 findById와 다른 점이 있다면, findById메서드가 실행되면 DB에서 해당 데이터가 존재하는지를 확인하기위해 select 쿼리가 날아간다. 반면에 getReferenceById는 select 쿼리를 날리지 않고 레퍼런스만 가져오게 된다.
따라서 이미 해당 ID를 가진 게시물이 존재한다는 것을 알고있다면 굳이 쿼리를 날려서 확인해볼 필요가 없으므로, 필요에 따라 사용하는 것이 효율적이다.
이 메소드는 getReferenceBy를 사용해서 작성한다.
public void updateArticle(ArticleDto dto) {
Article article = articleRepository.getReferenceById(dto.id());
if(dto.title() != null) { article.setTitle(dto.title());}
if(dto.content() != null) { article.setContent(dto.content());}
article.setHashtag(dto.hashtag());
}
hastag의 경우는 null값이어도 상관이 없지만, 제목과 내용은 null값이면 안된다. 따라서 방어기제를 위해 if문을 사용해서 null이 아닐 경우에만 데이터 값을 수정해주는 방식으로 작성해야한다.
또한 save문구도 작성하지 않았는데, 이는 @Transactional 어노테이션을 사용고 있으면 트랜잭션 단위로 테스트가 묶이게 되는데, 해당 테스트가 끝날 때마다 article의 변화를 자동으로 감지해서 update쿼리를 날리게 된다. 다만 코드로 명시를 하고 싶다면 save 문구를 작성해도 상관은 없으니 편할대로 할 것.
다시 테스트를 돌렸을 때 수정 테스트는 성공했지만, 없는 게시글의 수정 테스트는 실패로 나타나있다. 게시글이 없을 경우에 대한 대비가 되어있지 않은 상태인데, getReferenceById는 EntityNotFoundException을 던지는 것을 이용해서 try catch문을 작성한다.
lombok에서 @Slf4j를 사용하면 log문을 간단하게 작성할 수 있다.
public void updateArticle(ArticleDto dto) {
try {
Article article = articleRepository.getReferenceById(dto.id());
if (dto.title() != null) {
article.setTitle(dto.title());
}
if (dto.content() != null) {
article.setContent(dto.content());
}
article.setHashtag(dto.hashtag());
} catch (EntityNotFoundException e){
log.warn("게시글 업데이트 실패. 게시글을 찾을수 없습니다 - dto: {}", dto);
}
}
마지막으로 게시글을 삭제하는 메소드이다. 별거 없이 그냥 deleteById사용하면 끝난다.
public void deleteArticle(long articleId) {
articleRepository.deleteById(articleId);
}
이로써 게시글 서비스 테스트의 구현이 끝났다.