SNS 제작 (댓글 작성)

개발연습생log·2023년 1월 4일
0

SNS 제작

목록 보기
7/15
post-thumbnail

목표

📢 댓글 작성 기능을 구현해보자

접근 방법

  • JPA 연관관계
    • 한 유저는 여러 개의 댓글을 작성할 수 있다.
    • 한개의 포스트는 여러개의 댓글을 가실 수 있다.
    • 한개의 댓글은 한명의 작성자와 1개의 포스트 ID를 가질 수 있다.
    • 결론적으로 댓글과 유저 혹은 포스트의 관계는 다대일의 매핑관계를 가질 수 있다.
  • 댓글은 로그인을 한 유저만 작성할 수 있다.

핵심 키워드

  • JPA 다대일 단방향 매핑
  • Security FIlter 권한 설정

TO-DO

  • 댓글 작성 컨트롤러 테스트 작성
  • 댓글 작성 서비스 테스트 작성
  • 댓글 작성 컨트롤러 구현
  • 댓글 작성 서비스 구현
  • 댓글 작성 리포지토리 구현

댓글 작성 컨트롤러 테스트 작성

<@WebMvcTest(CommentController.class)
@MockBean(JpaMetamodelMappingContext.class)
class CommentControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    CommentService commentService;

    @Autowired
    ObjectMapper objectMapper;

    CommentResponse commentResponse = CommentResponse.builder()
            .id(1L)
            .comment("테스트입니다.")
            .userName("홍길동")
            .postId(1L)
            .createdAt(LocalDateTime.now())
            .build();

    @Test
    @DisplayName("댓글 작성 성공")
    @WithMockUser
    void comment_write_SUCCESS() throws Exception {

        when(commentService.write(any(), any(), any()))
                .thenReturn(commentResponse);

        mockMvc.perform(post("/api/v1/posts/1/comments")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new CommentRequest("테스트입니다."))))
                .andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    @DisplayName("댓글 작성 실패1_로그인을 하지 않은 경우")
    @WithMockUser
    void comment_write_FAILD_login() throws Exception {
        when(commentService.write(any(), any(), any()))
                .thenThrow(new AppException(ErrorCode.INVALID_PERMISSION, ErrorCode.INVALID_PERMISSION.getMessage()));

        mockMvc.perform(post("/api/v1/posts/1/comments")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new CommentRequest("테스트입니다."))))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("댓글 작성 실패2_포스트가 없는 경우")
    @WithMockUser
    void comment_write_FAILD_() throws Exception {
        when(commentService.write(any(), any(), any()))
                .thenThrow(new AppException(ErrorCode.POST_NOT_FOUND, ErrorCode.POST_NOT_FOUND.getMessage()));

        mockMvc.perform(post("/api/v1/posts/1/comments")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new CommentRequest("테스트입니다."))))
                .andDo(print())
                .andExpect(status().isNotFound());
    }
}

댓글 작성 서비스 테스트 구현

class CommentServiceTest {

    CommentService commentService;
    PostRepository postRepository = mock(PostRepository.class);
    UserRepository userRepository = mock(UserRepository.class);
    CommentRepository commentRepository = mock(CommentRepository.class);

    User user = User.builder()
            .userId(1L)
            .userName("홍길동")
            .password("0000")
            .build();

    Post post = Post.builder()
            .id(1L)
            .title("제목")
            .body("내용입니다.")
            .user(user)
            .build();

    Comment comment = Comment.builder()
            .id(1L)
            .comment("테스트입니다.")
            .user(user)
            .post(post)
            .build();

    @BeforeEach
    void setUp() {
        commentService = new CommentService(userRepository,postRepository,commentRepository);
    }

    @Test
    @DisplayName("댓글 작성 성공")
    void comment_write_SUCCESS() {
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.of(user));
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));
        when(commentRepository.save(any()))
                .thenReturn(comment);

        assertDoesNotThrow(() -> commentService.write(user.getUserName(),post.getId(),comment.getComment()));
    }

    @Test
    @DisplayName("댓글 작성 실패1_로그인을 안한 경우")
    void comment_write_FALID_login() {
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.empty());
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));
        when(commentRepository.save(any()))
                .thenReturn(comment);

        AppException exception = assertThrows(AppException.class, () -> commentService.write(user.getUserName(),post.getId(),comment.getComment()));
        assertEquals(ErrorCode.USERNAME_NOT_FOUND, exception.getErrorCode());
    }

    @Test
    @DisplayName("댓글 작성 실패2_포스트가 없는 경우")
    void comment_write_FALID_post() {
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.of(user));
        when(postRepository.findById(any()))
                .thenReturn(Optional.empty());
        when(commentRepository.save(any()))
                .thenReturn(comment);

        AppException exception = assertThrows(AppException.class, () -> commentService.write(user.getUserName(),post.getId(),comment.getComment()));
        assertEquals(ErrorCode.POST_NOT_FOUND, exception.getErrorCode());
    }
}

댓글 작성 컨트롤러 구현

CommentController

@RestController
@RequestMapping("/api/v1/posts/{postId}/comments")
@RequiredArgsConstructor
public class CommentController {
    private final CommentService commentService;

    @PostMapping
    public ResponseEntity<Response> write(Authentication authentication, @PathVariable Long postId, @RequestBody CommentRequest commentRequest) {
        String userName = authentication.getName();
        CommentResponse commentResponse = commentService.write(userName, postId, commentRequest.getComment());
        return ResponseEntity.ok().body(Response.of("SUCCESS", commentResponse));
    }
}

CommentDTO

  • CommentRequest
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class CommentRequest {
    private String comment;
}
  • CommentResponse
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class CommentResponse {
    private Long id;
    private String comment;
    private String userName;
    private Long postId;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd' 'HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime createdAt;

    public static CommentResponse of(Comment comment) {
        return CommentResponse.builder()
                .id(comment.getId())
                .comment(comment.getComment())
                .userName(comment.getUser().getUserName())
                .postId(comment.getPost().getId())
                .createdAt(comment.getCreatedAt())
                .build();
    }
}

댓글 작성 서비스 구현

CommentService

@Service
@RequiredArgsConstructor
public class CommentService {
    private final UserRepository userRepository;
    private final PostRepository postRepository;
    private final CommentRepository commentRepository;

    public CommentResponse write(String userName, Long postId, String comment) {
        //유저 체크
        User findUser = AppUtil.findUser(userRepository, userName);
        //포스트 체크
        Post findPost = AppUtil.findPost(postRepository, postId);
        //댓글 엔티티 변환 후 저장
        Comment saveComment = Comment.of(comment, findUser, findPost);
        saveComment = commentRepository.save(saveComment);
        //댓글 DTO 리턴
        return CommentResponse.of(saveComment);
    }
}

CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long> {
}

Comment

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Getter
public class Comment extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String comment;

    @ManyToOne
    @JoinColumn(name = "userId")
    private User user;

    @ManyToOne
    @JoinColumn(name = "postId")
    private Post post;

    public static Comment of(String comment, User user, Post post) {
        return Comment.builder()
                .comment(comment)
                .user(user)
                .post(post)
                .build();
    }
}

📖 회고록

  • mockMvc에 관해 예외가 발생했다.
  • perform함수의 content로 any()를 사용한 것이 원인이였다.
  • 하지만 발생한 이유는 알지 못했다.
  • mockMvc에 대해 자세한 공부가 필요하다고 느꼈다.
profile
주니어 개발자를 향해서..

0개의 댓글