새로운 파트 인 댓글 부분을 작성할 생각이다.
개발의 순서 에는 사람마다 의 취향 차이가 있다. 개인적으로 등록부터 하는 것을 좋아한다.
이렇게 해야 직접 테스트 해보면서 mysql에 쿼리도 날려보고 하면서 테스트하기 좋아서 이렇게 하는 걸 선호한다.
사실 제일 구현하기 간편하다. ㅎㅎㅎ 조회 가 페이징 과 querydsl 써서 해야 해서 하기 편한 등록 먼저 하고 조회를 하기로 했다 ㅎㅎ..
지난번 좋아요 와 싫어요 PR 이후로 컨탠트 쪽은 마무리되었다. 이번에는 댓글 파트 쪽을 다뤄 볼까 한다.
Pull로 떙겨오고, 동료 분은 Group Meeting 등록 쪽을 하셨다. 아무래도 시간을 다루고 서비스 핵심 로직이기에 코드가 쫌 많다. ㅎㅎ
git checkout guiwoo
M src/main/resources/application-dev.properties
Switched to branch 'guiwoo'
지난번과 동일하게 바로 컨트롤러 테스트 코드 먼저 작성하러 가자.
@Test
@DisplayName("멤버 피드 댓글 작성 성공")
public void contentCommentCreate() throws Exception{
ContentCommentCreate.Request req
= ContentCommentCreate.Request.builder().comment("test").build();
mockMvc.perform(post("/api/v1/member/content/3/comment")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req))
)
.andExpect(status().isOk())
.andExpect(
jsonPath("$.success")
.value("T")
)
.andDo(print());
}
댓글 등록이다 보니 댓글의 정보를 가져올 리퀘스트 바디가 필요하다.
erd의 댓글 필드를 확인해보고 오자. 음? content 만 받아오면 해결된다. 해결해줄 클래스 작성해 주러 가자.
public class ContentCommentCreate {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Request{
@NotNull(message = "내용은 필수 입력 사항입니다.")
private String comment;
}
}
요렇게 이너 클래스로 리퀘스트 지정해주자. 이번에 동료분 께서 알려주신 @NotBlank 가 가장 인텐시브 하게 체크를 한다고 한다.
이참에 정리해보자 @NotEmpty와 @NotNull을 @NotBlank 프로젝트 진행하면서 이것저것 사용했던 거 같은데 이런 인텐시브 체킹
관련해서는 상관없이 그냥 직관적인 이름에 따라 사용해왔다.
바로 테스트해보자 궁금하니깐 @NotBlank 타고 올라가 보자
The annotated element must not be null and must contain at least one non-whitespace character. Accepts CharSequence.
오 null 값이 아니고 적어도 하나 이상의 공백이 아닌 문자를 포함하고 있어야 한다고 한다. @NotNull 도 확인해보자.
The annotated element must not be null. Accepts any type.
눌 만 아니면 어느 타입이든 받아들인다고 심플하게 적혀있다.
테스트를 해보자.
/** 테스트 시작점 위쪽에 걸어 놓은 부분
private static Validator validator;
@BeforeAll
public static void init() {
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
*/
@Test
public void NotBlankTest() throws Exception{
ContentCommentCreate.Request req =
ContentCommentCreate.Request.builder().comment("Test").build();
var test1
= validator.validate(req);
assertThat(test1.size()).isEqualTo(0);
req.setComment("");
var test2 = validator.validate(req);
assertThat(test2.size()).isEqualTo(1);
req.setComment(" ");
var test3 = validator.validate(req);
assertThat(test3.size()).isEqualTo(1);
req.setComment(null);
var test4 = validator.validate(req);
assertThat(test4.size()).isEqualTo(1);
}
와 이 테스트가 통과된다. 공백 인 배분을 모조리 체크해준다. 서비스 구현이 마무리되는 대로 NotNull 과 NotEmpty 를 섞어서 사용했는데 모두 NotBlank 로 통일해 주어야 겠다.
이제 진짜 컨트롤러 테스트 하기 위한 코드를 작성해주자.
@PostMapping("{memberContentId}/comment")
public ResponseEntity<?> contentComment(
@PathVariable("memberContentId") Long contentId,
@RequestBody @Valid ContentCommentCreate.Request req,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new CustomMethodArgumentNotValidException(bindingResult);
}
// memberContentService.contentCommentCreate(req);
return new ResponseEntity<>(
CommonResponse.ok(),
HttpStatus.OK
);
}
바디의 값과, 성공 사례 총 2가지 의 경우를 테스트해주었다. 이제 서비스로 넘어가자.
서비스 테스트 코드
@Nested
@DisplayName("멤버 피드 댓글 등록 테스트")
class MemberFeedContent{
Long contentId = 169L;
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [로그인 유저 미 일치]")
public void doesNotMatchUser() throws Exception {
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [피드 가 존재하지 않는 경우]")
public void doesNotExistFeed() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [피드가 삭제된 경우]")
public void feedDeleted() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 댓글 등록 성공")
public void contentCommentCreateSuccess() throws Exception{
//given
//when
//then
}
}
1번 ~ 3번까지는 지난번 테스트 와 동일하니 코드만 첨부한다.
1번 로그인 유저 미 일치
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [로그인 유저 미 일치]")
public void doesNotMatchUser() throws Exception {
//given
given(commonRequestContext.getMemberEmail()).willReturn("True-Lover");
//when
MemberException exception = assertThrows(MemberException.class,
()->memberContentService.contentCommentCreate(req,contentId));
//then
assertEquals(MemberErrorCode.MEMBER_EMAIL_ERROR,exception.getErrorCode());
}
// 서비스
@Override
public void contentCommentCreate(ContentCommentCreate.Request req, Long contentId) {
Member m = validCheckLoggedInUser();
}
2번 피드 가 존재하지 않는 경우
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [피드 가 존재하지 않는 경우]")
public void doesNotExistFeed() throws Exception{
doReturn(Optional.of(Member.builder().build()))
.when(memberRepository).findByEmail(any());
//when
MemberException exception = assertThrows(MemberException.class,
()->memberContentService.contentCommentCreate(req,contentId));
//then
assertEquals(MemberErrorCode.MEMBER_CONTENT_DOES_NOT_EXIST
,exception.getErrorCode());
}
// servcie
@Override
public void contentCommentCreate(ContentCommentCreate.Request req, Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
}
3번 피드 가 삭제된 경우
@Test
@DisplayName("멤버 피드 댓글 등록 실패 [피드가 삭제된 경우]")
public void feedDeleted() throws Exception{
//given
doReturn(Optional.of(Member.builder().build()))
.when(memberRepository).findByEmail(any());
doReturn(Optional.of(MemberContent.builder().deletedYn(true).build()))
.when(memberContentRepository).findById(any());
//when
MemberException exception = assertThrows(MemberException.class,
()-> memberContentService.contentLikeCancel(12L) );
//then
assertEquals(MemberErrorCode.MEMBER_CONTENT_DELETED,
exception.getErrorCode());
}
// servcie
@Override
public void contentCommentCreate(ContentCommentCreate.Request req, Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
isContentDeleted(mc);
}
private void isContentDeleted(MemberContent mc){
if(mc.isDeletedYn()){
throw new MemberException(MEMBER_CONTENT_DELETED);
}
}
1~3번 모두 통과 완료! 이제 성공 케이스만 테스트하면 된다 바로 가보자.
지난번에 말한 대로 저장할 값을 캡처 해와 벨류 확인을 하는 작업을 추가하고
제일 좋아하는 verify times를 이용해 함수의 실행이 마지막까지 실행되는지 확인했다.
서비스 코드 작성하러 가보자.
@Override
public void contentCommentCreate(ContentCommentCreate.Request req, Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
isContentDeleted(mc);
MemberContentComment mcc = MemberContentComment.builder()
.member(m)
.memberContent(mc)
.content(req.getComment())
.deletedYn(false)
.build();
memberContentCommentRepository.save(mcc);
}
지난번 과제 테스트로 롬복 없이 계좌 시스템을 구현하는 경우가 있었는데 롬복의 소중함 매번 느낀다 ㅎㅎ
테스트는 모두 통과된다.
서버 올리고 테스트해보자
로그인해서 토큰 받아주고
헤더에 토큰 넣어주고 content-type 넣어주고
응?
게시글이 없네 🥲 4번으로 보내자
예이 200 리스폰스 받았다. ㅎㅎ
// 멤버 찾아주고
select
member0_.member_id as member_i1_12_,
member0_.created_at as created_2_12_,
member0_.updated_at as updated_3_12_,
member0_.deleted_at as deleted_4_12_,
member0_.email as email5_12_,
member0_.member_status as member_s6_12_,
member0_.nickname as nickname7_12_,
member0_.password as password8_12_,
member0_.phone_number as phone_nu9_12_,
member0_.profile_img as profile10_12_,
member0_.status as status11_12_,
member0_.username as usernam12_12_
from
member member0_
where
member0_.email=?
// 컨탠트 찾아주고
select
membercont0_.member_content_id as member_c1_15_0_,
membercont0_.created_at as created_2_15_0_,
membercont0_.updated_at as updated_3_15_0_,
membercont0_.content as content4_15_0_,
membercont0_.deleted_at as deleted_5_15_0_,
membercont0_.deleted_yn as deleted_6_15_0_,
membercont0_.member_id as member_10_15_0_,
membercont0_.notice_yn as notice_y7_15_0_,
membercont0_.sort_value as sort_val8_15_0_,
membercont0_.title as title9_15_0_
from
member_content membercont0_
where
membercont0_.member_content_id=?
// save 해주구
insert
into
member_content_comment
(created_at, updated_at, content, deleted_at, deleted_yn, member_id, member_content_id)
values
(?, ?, ?, ?, ?, ?, ?)
원하는 쿼리 개수만큼 나갔다 의도한 대로 작성이 되었다.
마지막 확인을 위한 mysql_workBench 확인 좋다.
테스트를 하는데 이렇게 또 서버를 띄우고 이렇게 여러 번 확인해도 항상 실제 서버 배포하고 그러면 오류가 난다. 그러면 배포를 다시 하고 그래야 하는데 우리 같은 작은 구조의 서비스 면 금방 배포가 되겠지만 그게 아니라면? 끔찍하다 ㅎㅎ 이래서 대부분의 서비스는 배포용 서버와 테스트용 서버 두 개 운용을 한다고 들었는데 그래도 배포하고 이런 게 다 돈아 니겠는가? 간단히 핸드폰 게임만 해도 긴급 점검 이 빈번하게 일어나는 경우가 많다. 왜 그러겠는가 수많은 개발자들이 확인했는데도 확인하지 못한 에러가 존재하기 때문이다. 이런 전문가들도 놓치는 부분이 있는데 하물며 나라고 없겠는가? 내가 생각하는 예외가 전부라고 생각하지 말자.
이번 프로젝트 진행하면서 처음 몇 개의 api 구현하는 동안 와 완벽하다 테스트 도 다통과 하고 그랬는데 나중에 보니 다른 테이블 데이터를 넣어주지 않는 바보 같은 실수를 해서 디버그 찍으면서 왜 안 들어가지 테스트가 들어가는데 ? 서버를 띄우고 하면 왜 안들어가지 이러면서 많은 삽질을 했다. ㅎㅎㅎㅎ
테스트 도 내가 작성하고 예외도 내가 생각하기 때문에, 테스트 가 통과 가 되는 것이다.
열린 마음으로 에러를 받아들이자 스트레스 그만 받자 🧐
긴 글 읽어주셔서 감사합니다.
'사이드 프로젝트 > 워크듀오-개발일지' 카테고리의 다른 글
멤버 피드 댓글 삭제 API (1) | 2022.09.28 |
---|---|
멤버 피드 댓글 업데이트 API (0) | 2022.09.28 |
멤버 피드 댓글 조회 API (1) | 2022.09.28 |
멤버 피드 좋아요 API 구현 (0) | 2022.09.27 |
멤버 피드 삭제 API 구현 (1) | 2022.09.25 |