게시판 만들기 - 게시판 페이지 기능 구현 2

정영찬·2022년 8월 23일
0

프로젝트 실습

목록 보기
35/60
post-thumbnail

이번에는 게시글 컨트롤러와 컨트롤러 테스트에 내용을 추가한다.

컨트롤러에서는 이제 저번에 구현했던 서비스를 본격적으로 사용할 것이다.
ArticleController

 private final ArticleService articleService;

서비스를 읽어서 정보전달을 하기위해 작성했지만 컨트롤러 테스트를 진행할때는 배제를 시켜서 MockMvc가 순수하게 api의 입출력만을 확인하기 위해 @MockBean을 사용해서 서비스를 배제한다.
ArticleControllerTest

 @MockBean private ArticleService articleService;

테스트 작성

게시글 리스트 페이지 정상호출

게시글 리스트를 조회하기 위해서 articleService로부터 게시글을 받아와야한다.
처음에 게시글 리스트를 조회하게 되면 searchArticles에서 searchType과 searchKeyword의 값이 모두 null상태에서 조회를 시작하게 된다.

given(articleService.searchArticles(eq(null),eq(null),any(Pageable.class))).willReturn(Page.empty());

필드중 일부만 matcher가 불가능하기 때문에 null 대신에 eq(null)로 수정하면 오류가 발생하지않는다.

이제 뷰에서 데이터,뷰의 존재여부, 그리고 모델 어트리뷰트로 넣어준 데이터 존재여부 검사를 하고 난다음에 articleService에서 searchArticles가 호출되었는지 확인한다.

public void givenNothing_whenRequestingArticlesView_thenReturnsArticlesView() throws Exception {
        // Given
       given(articleService.searchArticles(eq(null),eq(null),any(Pageable.class))).willReturn(Page.empty());
        // When & Then
        mvc.perform(get("/articles"))
                .andExpect(status().isOk()) // 정상 호출
                .andExpect(result -> content().contentType(MediaType.TEXT_HTML)) // 데이터 확인
                .andExpect(view().name("articles/index")) // 뷰의 존재여부 검사
                .andExpect(model().attributeExists("articles")); // 뷰에 모델 어트리뷰트로 넣어준 데이터존재 여부 검사
        then(articleService).should().searchArticles(eq(null),eq(null),any(Pageable.class));
    }

게시글 상세 페이지 정상호출

게시글 id를 임의로 선언하고, 게시글과 댓글관련 데이터가 들어있는 Dto를 생성하는 메소드를 호출한다. 해당 메소드는 이전에 서비스 테스트에서 사용한 픽스쳐를 사용하면 된다. mvc.perform에서 경로를 1에서 articleId로 수정해준다.

public void givenNothing_whenRequestingArticleView_thenReturnsArticleView() throws Exception {
        // Given
        Long articleId = 1L;
        given(articleService.getArticle(articleId)).willReturn(createArticleCommentsDto());
        // When & Then
        mvc.perform(get("/articles/" + articleId))
                .andExpect(status().isOk()) // 정상 호출
                .andExpect(result -> content().contentType(MediaType.TEXT_HTML)) // 데이터 확인
                .andExpect(view().name("articles/detail")) // 뷰의 존재여부 검사
                .andExpect(model().attributeExists("article")) // 뷰에 모델 어트리뷰트로 넣어준 데이터존재 여부 검사
                .andExpect(model().attributeExists("articleComments"))
        then(articleService).should().getArticle(articleId);
    }
private ArticleWithCommentsDto createArticleCommentsDto() {
        return ArticleWithCommentsDto.of(
                1L,
                createUserAccountDto(),
                Set.of(),
                "title",
                "content",
                "#java",
                LocalDateTime.now(),
                "jyc",
                LocalDateTime.now(),
                "jyc"

        );
    }

    private UserAccountDto createUserAccountDto() {
        return UserAccountDto.of(
                1L,
                "jyc",
                "pw",
                "jyc@mail.com",
                "Jyc",
                "memo",
                LocalDateTime.now(),
                "jyc",
                LocalDateTime.now(),
                "jyc"
        );
    }

게시글 검색 전용 페이지는 추후에 작성할 예정.

컨트롤러 작성

이제 컨트롤러에 내용을 추가해서 테스트를 통과시키게 만든다.

게시글 리스트 페이지 호출

@RequestParam을 이용해서 getparameter를 불러온다. 처음에 조회를 할때는 검색타입이나, 검색어가 null일수 있기 때문에 required는 false로 지정한다. pageable의 경우 @PageableDefault를 사용해서 작성하고 페이지 기본값은 10개, 최신순으로 게시글을 정렬한 상태로 나타나게 설정했다.

  @GetMapping
    public String articles(
            @RequestParam(required = false) SearchType searchType,
            @RequestParam(required = false) String searchValue,
            @PageableDefault(size = 10, sort ="createdAt", direction = Sort.Direction.DESC) Pageable pageable,
            ModelMap map) {
        map.addAttribute("articles", articleService.searchArticles(searchType,searchValue,pageable).map(ArticleResponse::from));
        return "articles/index";
    }

게시글 상세 페이지 호출

/{articleId}매핑에서 article의 어트리뷰트로 "article"이라는 가짜값을 집어넣었었는데, 이값 대신 ArticleWithCommentsResponse에서 데이터를 가져온다.

@GetMapping("/{articleId}")
    public String article(@PathVariable Long articleId, ModelMap map) {
        ArticleWithCommentsResponse article = ArticleWithCommentsResponse.from(articleService.getArticle(articleId));
        map.addAttribute("article", article);
        map.addAttribute("articleComments", article.articleCommentsResponse());
        return "articles/detail";
    }
package com.jycproject.bulletinboard.dto.response;

import com.jycproject.bulletinboard.dto.ArticleWithCommentsDto;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;

public record ArticleWithCommentsResponse(
        Long id,
        String title,
        String content,
        String hashtag,
        LocalDateTime createdAt,
        String email,
        String nickname,
        Set<ArticleCommentResponse> articleCommentsResponse

) implements Serializable {

    public static ArticleWithCommentsResponse of(Long id, String title, String content, String hashtag, LocalDateTime createdAt, String email, String nickname, Set<ArticleCommentResponse> articleCommentResponse) {
        return new ArticleWithCommentsResponse(id, title, content, hashtag, createdAt, email, nickname, articleCommentResponse);
    }
    public static ArticleWithCommentsResponse from(ArticleWithCommentsDto dto){
        String nickname = dto.userAccountDto().nickname();
        if (nickname == null || nickname.isBlank()) {
            nickname = dto.userAccountDto().userId();
        }
        return new ArticleWithCommentsResponse(
                dto.id(),
                dto.title(),
                dto.content(),
                dto.hashtag(),
                dto.createdAt(),
                dto.userAccountDto().email(),
                nickname,
                dto.articleCommentDtos().stream()
                        .map(ArticleCommentResponse::from)
                        .collect(Collectors.toCollection(LinkedHashSet::new))
        );
    }

}

이제 컨트롤러에서 실질적으로 데이터를 올려주는 테스트는 통과했다.

profile
개발자 꿈나무

0개의 댓글