#16 board : article comment controller impl, test

This commit is contained in:
haerong22
2022-08-22 03:47:44 +09:00
parent eb9bf718d6
commit 5ce977e362
6 changed files with 177 additions and 5 deletions

View File

@@ -0,0 +1,44 @@
package com.example.board.controller;
import com.example.board.dto.UserAccountDto;
import com.example.board.dto.request.ArticleCommentRequest;
import com.example.board.service.ArticleCommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@RequiredArgsConstructor
@RequestMapping("/comments")
@Controller
public class ArticleCommentController {
private final ArticleCommentService articleCommentService;
@PostMapping("/new")
public String postNewArticleComment(ArticleCommentRequest articleCommentRequest) {
// TODO: 인증 정보 필요
articleCommentService.saveArticleComment(articleCommentRequest.toDto(
UserAccountDto.of(
"bobby",
"1234",
"bobby@email.com",
null,
null
)
));
return "redirect:/articles/" + articleCommentRequest.articleId();
}
@PostMapping("/{commentId}/delete")
public String deleteArticleComment(@PathVariable Long commentId,
Long articleId) {
articleCommentService.deleteArticleComment(commentId);
return "redirect:/articles/" + articleId;
}
}

View File

@@ -2,6 +2,7 @@ package com.example.board.dto;
import com.example.board.domain.Article;
import com.example.board.domain.ArticleComment;
import com.example.board.domain.UserAccount;
import java.time.LocalDateTime;
@@ -15,6 +16,11 @@ public record ArticleCommentDto(
LocalDateTime modifiedAt,
String modifiedBy
) {
public static ArticleCommentDto of(Long articleId, UserAccountDto userAccountDto, String content) {
return new ArticleCommentDto(null, articleId, userAccountDto, content, null, null, null, null);
}
public static ArticleCommentDto of(Long id, Long articleId, UserAccountDto userAccountDto, String content, LocalDateTime createdAt, String createdBy, LocalDateTime modifiedAt, String modifiedBy) {
return new ArticleCommentDto(id, articleId, userAccountDto, content, createdAt, createdBy, modifiedAt, modifiedBy);
}
@@ -32,10 +38,10 @@ public record ArticleCommentDto(
);
}
public ArticleComment toEntity(Article entity) {
public ArticleComment toEntity(Article article, UserAccount userAccount) {
return ArticleComment.of(
entity,
userAccountDto.toEntity(),
article,
userAccount,
content
);
}

View File

@@ -0,0 +1,19 @@
package com.example.board.dto.request;
import com.example.board.dto.ArticleCommentDto;
import com.example.board.dto.UserAccountDto;
public record ArticleCommentRequest(Long articleId, String content) {
public static ArticleCommentRequest of(Long articleId, String content) {
return new ArticleCommentRequest(articleId, content);
}
public ArticleCommentDto toDto(UserAccountDto userAccountDto) {
return ArticleCommentDto.of(
articleId,
userAccountDto,
content
);
}
}

View File

@@ -1,9 +1,12 @@
package com.example.board.service;
import com.example.board.domain.Article;
import com.example.board.domain.ArticleComment;
import com.example.board.domain.UserAccount;
import com.example.board.dto.ArticleCommentDto;
import com.example.board.repository.ArticleCommentRepository;
import com.example.board.repository.ArticleRepository;
import com.example.board.repository.UserAccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -20,6 +23,7 @@ public class ArticleCommentService {
private final ArticleRepository articleRepository;
private final ArticleCommentRepository articleCommentRepository;
private final UserAccountRepository userAccountRepository;
@Transactional(readOnly = true)
public List<ArticleCommentDto> searchArticleComments(Long articleId) {
@@ -31,9 +35,11 @@ public class ArticleCommentService {
public void saveArticleComment(ArticleCommentDto dto) {
try {
articleCommentRepository.save(dto.toEntity(articleRepository.getReferenceById(dto.articleId())));
Article article = articleRepository.getReferenceById(dto.articleId());
UserAccount userAccount = userAccountRepository.getReferenceById(dto.userAccountDto().userId());
articleCommentRepository.save(dto.toEntity(article,userAccount));
} catch (EntityNotFoundException e) {
log.warn("댓글 저장 실패. 댓글의 게시글을 찾을 수 없습니다 - dto: {}", dto);
log.warn("댓글 저장 실패. 댓글 작성에 필요한 정보를 찾을 수 없습니다 - dto: {}", e.getLocalizedMessage());
}
}

View File

@@ -0,0 +1,89 @@
package com.example.board.controller;
import com.example.board.config.SecurityConfig;
import com.example.board.dto.ArticleCommentDto;
import com.example.board.dto.request.ArticleCommentRequest;
import com.example.board.service.ArticleCommentService;
import com.example.board.util.FormDataEncoder;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Map;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@DisplayName("View 컨트롤러 - 댓글")
@Import({SecurityConfig.class, FormDataEncoder.class})
@WebMvcTest(ArticleCommentController.class)
class ArticleCommentControllerTest {
private final MockMvc mockMvc;
private final FormDataEncoder formDataEncoder;
@MockBean
private ArticleCommentService articleCommentService;
public ArticleCommentControllerTest(
@Autowired MockMvc mockMvc,
@Autowired FormDataEncoder formDataEncoder
) {
this.mockMvc = mockMvc;
this.formDataEncoder = formDataEncoder;
}
@DisplayName("[view][POST] 댓글 등록 - 정상 호출")
@Test
void givenArticleCommentInfo_whenRequesting_thenSavesNewArticleComment() throws Exception {
// Given
long articleId = 1L;
ArticleCommentRequest request = ArticleCommentRequest.of(articleId, "test comment");
willDoNothing().given(articleCommentService).saveArticleComment(any(ArticleCommentDto.class));
// When & Then
mockMvc.perform(
post("/comments/new")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.content(formDataEncoder.encode(request))
.with(csrf())
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/articles/" + articleId))
.andExpect(redirectedUrl("/articles/" + articleId));
then(articleCommentService).should().saveArticleComment(any(ArticleCommentDto.class));
}
@DisplayName("[view][POST] 댓글 삭제 - 정상 호출")
@Test
void givenArticleCommentIdToDelete_whenRequesting_thenDeletesArticleComment() throws Exception {
// Given
long articleId = 1L;
long articleCommentId = 1L;
String userId = "unoTest";
willDoNothing().given(articleCommentService).deleteArticleComment(articleCommentId);
// When & Then
mockMvc.perform(
post("/comments/" + articleCommentId + "/delete")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.content(formDataEncoder.encode(Map.of("articleId", articleId)))
.with(csrf())
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/articles/" + articleId))
.andExpect(redirectedUrl("/articles/" + articleId));
then(articleCommentService).should().deleteArticleComment(articleCommentId);
}
}

View File

@@ -7,6 +7,7 @@ import com.example.board.dto.ArticleCommentDto;
import com.example.board.dto.UserAccountDto;
import com.example.board.repository.ArticleCommentRepository;
import com.example.board.repository.ArticleRepository;
import com.example.board.repository.UserAccountRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -34,6 +35,9 @@ class ArticleCommentServiceTest {
@Mock
private ArticleCommentRepository articleCommentRepository;
@Mock
private UserAccountRepository userAccountRepository;
@DisplayName("게시글 ID로 조회하면 해당하는 댓글 리스트를 반환한다.")
@Test
void givenArticleId_whenSearchingArticleComments_thenReturnsArticleComments() {
@@ -60,12 +64,15 @@ class ArticleCommentServiceTest {
// Given
ArticleCommentDto dto = createArticleCommentDto("댓글");
given(articleRepository.getReferenceById(dto.articleId())).willReturn(createArticle());
given(userAccountRepository.getReferenceById(dto.userAccountDto().userId())).willReturn(createUserAccount());
given(articleCommentRepository.save(any(ArticleComment.class))).willReturn(null);
// When
sut.saveArticleComment(dto);
// Then
then(articleRepository).should().getReferenceById(dto.articleId());
then(userAccountRepository).should().getReferenceById(dto.userAccountDto().userId());
then(articleCommentRepository).should().save(any(ArticleComment.class));
}
@@ -81,6 +88,7 @@ class ArticleCommentServiceTest {
// Then
then(articleRepository).should().getReferenceById(dto.articleId());
then(userAccountRepository).shouldHaveNoInteractions();
then(articleCommentRepository).shouldHaveNoInteractions();
}