์ด๋ฒ์๋ ๋๊ธ์ ์ ์ฅ, ์์ ,์ญ์ ํ๋ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด๊ฒ๋ง ๋ง๋ค๋ฉด ๊ทธ๋๋ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋ฅ์ ๊ฐ์ง ๊ฒ์ํ ์๋น์ค๋ฅผ ์์ฑํ๋ค์.
- ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ JSON ๋ฐ์ดํฐ๋ก ๋ก๊ทธ์ธ (์๋ฃ)
- JWT๋ฅผ ์ด์ฉํ ์ธ์ฆ (์๋ฃ)
- ๋๋ฉ์ธ, ํ ์ด๋ธ ์ค๊ณ, ์ํฐํฐ ์์ฑ (์๋ฃ)
- ๋๊ธ ์ญ์ ๋ก์ง ๊ตฌํ (์๋ฃ)
- ํ์๊ฐ์ + ์ ๋ณด์์ ๋ฑ ํ์ ์๋น์ค ๊ตฌํ (์๋ฃ)
- ๊ฒ์ํ ์๋น์ค ๊ตฌํ (์งํ ์ค)
- ๋๊ธ ์๋น์ค ๊ตฌํ (1๋๊ธ -> *(๋ฌดํ) ๋๋๊ธ ๊ตฌ์กฐ) (์๋ฃ)
- ์์ธ ์ฒ๋ฆฌ (์๋ฃ)
- ์์ธ ๋ฉ์ธ์ง ๊ตญ์ ํ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๊ฒ์ํ ๋ถ๋ฅ
- ๊ฒ์๊ธ ํ์ด์ง (์๋ฃ)
- ๋์ ์ธ ๊ฒ์ ์กฐ๊ฑด์ ์ฌ์ฉํ ๊ฒ์ (์๋ฃ)
- ์ฌ์ฉ์ ๊ฐ ์ชฝ์ง ๊ธฐ๋ฅ
- ๋ฌดํ ์ชฝ์ง ์คํฌ๋กค
- ๊ฒ์๋ฌผ & ๋๊ธ์ ๋ํ ์๋
- ์ชฝ์ง์ ๋ํ ์๋
- ์ ์ํ ์ฌ์ฉ์ ๊ฐ ์ค์๊ฐ ์ฑํ
- ํ์๊ฐ์ ์ ๊ฒ์ฆ(์: XX๋ํ๊ต XX๊ณผ๊ฐ ์๋๋ฉด ๊ฐ์ ํ ์ ์๊ฒ)
- Swagger๋ฅผ ์ฌ์ฉํ API ๋ฌธ์ ๋ง๋ค๊ธฐ
- ์ ๊ณ & ๋ธ๋๋ฆฌ์คํธ ๊ธฐ๋ฅ
- AOP๋ฅผ ํตํ ๋ก๊ทธ
- ์ด๋๋ฏผ ํ์ด์ง
- ์บ์
- ๋ฐฐํฌ (+ ๋ฌด์ค๋จ ๋ฐฐํฌ)
- ๋ฐฐํฌ ์๋ํ
- ํฌํธ์ ์ด๋ํฐ ์ค๊ณ๋ฅผ ๋ฐ๋ฅด๋ ํจํค์ง ๊ตฌ์กฐ ์ค๊ณํ๊ธฐ
- ...
CommentController ๋ง๋ค๊ธฐ
์์น๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@RestController
@RequiredArgsConstructor
public class CommentController {
private final CommentService commentService;
@PostMapping("/comment/{postId}")
@ResponseStatus(HttpStatus.CREATED)
public void commentSave(@PathVariable("postId") Long postId, CommentSaveDto commentSaveDto){
commentService.save(postId, commentSaveDto);
}
@PostMapping("/comment/{postId}/{commentId}")
@ResponseStatus(HttpStatus.CREATED)
public void reCommentSave(@PathVariable("postId") Long postId,
@PathVariable("commentId") Long commentId,
CommentSaveDto commentSaveDto){
commentService.saveReComment(postId, commentId, commentSaveDto);
}
@PutMapping("/comment/{commentId}")
public void update(@PathVariable("commentId") Long commentId,
CommentUpdateDto commentUpdateDto){
commentService.update(commentId, commentUpdateDto);
}
@DeleteMapping("/comment/{commentId}")
public void delete(@PathVariable("commentId") Long commentId){
commentService.remove(commentId);
}
}
์ด์ ์ด์ ๋ํ ํ ์คํธ์ฝ๋๋ฅผ ์์ฑํ๋๋ก ํ๊ฒ ์ต๋๋ค.
ํ ์คํธ์ฝ๋ ์์ฑ
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class CommentControllerTest {
@Autowired MockMvc mockMvc;
@Autowired EntityManager em;
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Autowired PostRepository postRepository;
@Autowired
CommentService commentService;
@Autowired
CommentRepository commentRepository;
ObjectMapper objectMapper = new ObjectMapper();
@Autowired JwtService jwtService;
final String USERNAME = "username1";
private static Member member;
@BeforeEach
private void signUpAndSetAuthentication() throws Exception {
member = memberRepository.save(Member.builder().username(USERNAME).password("1234567890").name("USER1").nickName("๋ฐฅ ์๋จน๋ ๋ํ์ด1").role(Role.USER).age(22).build());
SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
emptyContext.setAuthentication(
new UsernamePasswordAuthenticationToken(
User.builder()
.username(USERNAME)
.password("1234567890")
.roles(Role.USER.toString())
.build(),
null)
);
SecurityContextHolder.setContext(emptyContext);
clear();
}
private void clear() {
em.flush();
em.clear();
}
private String getAccessToken(){
return jwtService.createAccessToken(USERNAME);
}
private String getNoAuthAccessToken(){
return jwtService.createAccessToken(USERNAME+12);
}
private Long savePost(){
String title = "์ ๋ชฉ";
String content = "๋ด์ฉ";
PostSaveDto postSaveDto = new PostSaveDto(title, content, Optional.empty());
//when
Post save = postRepository.save(postSaveDto.toEntity());
clear();
return save.getId();
}
private Long saveComment(){
CommentSaveDto commentSaveDto = new CommentSaveDto("๋๊ธ");
commentService.save(savePost(),commentSaveDto);
clear();
List<Comment> resultList = em.createQuery("select c from Comment c order by c.createdDate desc ", Comment.class).getResultList();
return resultList.get(0).getId();
}
private Long saveReComment(Long parentId){
CommentSaveDto commentSaveDto = new CommentSaveDto("๋๋๊ธ");
commentService.saveReComment(savePost(),parentId,commentSaveDto);
clear();
List<Comment> resultList = em.createQuery("select c from Comment c order by c.createdDate desc ", Comment.class).getResultList();
return resultList.get(0).getId();
}
@Test
public void ๋๊ธ์ ์ฅ_์ฑ๊ณต() throws Exception {
//given
Long postId = savePost();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "comment");
//when
mockMvc.perform(
post("/comment/"+postId)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isCreated());
//then
List<Comment> resultList = em.createQuery("select c from Comment c order by c.createdDate desc ", Comment.class).getResultList();
assertThat(resultList.size()).isEqualTo(1);
}
@Test
public void ๋๋๊ธ์ ์ฅ_์ฑ๊ณต() throws Exception {
//given
Long postId = savePost();
Long parentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "recomment");
//when
mockMvc.perform(
post("/comment/"+postId+"/"+parentId)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isCreated());
//then
List<Comment> resultList = em.createQuery("select c from Comment c order by c.createdDate desc ", Comment.class).getResultList();
assertThat(resultList.size()).isEqualTo(2);
}
@Test
public void ๋๊ธ์ ์ฅ_์คํจ_๊ฒ์๋ฌผ์ด_์์() throws Exception {
//given
Long postId = savePost();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "comment");
//when,then
mockMvc.perform(
post("/comment/"+1000000)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isNotFound());
}
@Test
public void ๋๋๊ธ์ ์ฅ_์คํจ_๊ฒ์๋ฌผ์ด_์์() throws Exception {
//given
Long postId = savePost();
Long parentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "recomment");
//when,then
mockMvc.perform(
post("/comment/"+10000+"/"+parentId)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isNotFound());
}
@Test
public void ๋๋๊ธ์ ์ฅ_์คํจ_๋๊ธ์ด_์์() throws Exception {
//given
Long postId = savePost();
Long parentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "recomment");
//when,then
mockMvc.perform(
post("/comment/"+postId+"/"+10000)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isNotFound());
}
@Test
public void ์
๋ฐ์ดํธ_์ฑ๊ณต() throws Exception {
//given
Long postId = savePost();
Long commentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "updateComment");
//when
mockMvc.perform(
put("/comment/"+commentId)
.header("Authorization", "Bearer "+ getAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isOk());
Comment comment = commentRepository.findById(commentId).orElse(null);
assertThat(comment.getContent()).isEqualTo("updateComment");
}
@Test
public void ์
๋ฐ์ดํธ_์คํจ_๊ถํ์ด์์() throws Exception {
//given
Long postId = savePost();
Long commentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "updateComment");
//when
mockMvc.perform(
put("/comment/"+commentId)
.header("Authorization", "Bearer "+ getNoAuthAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isForbidden());
Comment comment = commentRepository.findById(commentId).orElse(null);
assertThat(comment.getContent()).isEqualTo("๋๊ธ");
}
@Test
public void ๋๊ธ์ญ์ _์คํจ_๊ถํ์ด_์์() throws Exception {
//given
Long postId = savePost();
Long commentId = saveComment();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("content", "updateComment");
//when
mockMvc.perform(
delete("/comment/"+commentId)
.header("Authorization", "Bearer "+ getNoAuthAccessToken())
.contentType(MediaType.MULTIPART_FORM_DATA).params(map))
.andExpect(status().isForbidden());
Comment comment = commentRepository.findById(commentId).orElse(null);
assertThat(comment.getContent()).isEqualTo("๋๊ธ");
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋๋๊ธ์ด ๋จ์์๋ ๊ฒฝ์ฐ
// DB์ ํ๋ฉด์์๋ ์ง์์ง์ง ์๊ณ , "์ญ์ ๋ ๋๊ธ์
๋๋ค"๋ผ๊ณ ํ์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
saveReComment(commentId);
saveReComment(commentId);
saveReComment(commentId);
saveReComment(commentId);
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getChildList().size()).isEqualTo(4);
//when
mockMvc.perform(
delete("/comment/"+commentId)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
//then
Comment findComment = commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT));
assertThat(findComment).isNotNull();
assertThat(findComment.isRemoved()).isTrue();
assertThat(findComment.getChildList().size()).isEqualTo(4);
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
//๋๋๊ธ์ด ์์ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ : ๊ณง๋ฐ๋ก DB์์ ์ญ์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
//when
mockMvc.perform(
delete("/comment/"+commentId)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
clear();
//then
Assertions.assertThat(commentRepository.findAll().size()).isSameAs(0);
assertThat(assertThrows(CommentException.class, () ->commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).getExceptionType()).isEqualTo(CommentExceptionType.NOT_POUND_COMMENT);
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋๋๊ธ์ด ์กด์ฌํ๋ ๋ชจ๋ ์ญ์ ๋ ๊ฒฝ์ฐ
//๋๊ธ๊ณผ, ๋ฌ๋ ค์๋ ๋๋๊ธ ๋ชจ๋ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์๋ ํ์๋์ง ์์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_์กด์ฌํ๋_๋ชจ๋_์ญ์ ๋_๋๋๊ธ์ธ_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
Long reCommend4Id = saveReComment(commentId);
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getChildList().size()).isEqualTo(4);
clear();
commentService.remove(reCommend1Id);
clear();
commentService.remove(reCommend2Id);
clear();
commentService.remove(reCommend3Id);
clear();
commentService.remove(reCommend4Id);
clear();
Assertions.assertThat(commentRepository.findById(reCommend1Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
Assertions.assertThat(commentRepository.findById(reCommend2Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
Assertions.assertThat(commentRepository.findById(reCommend3Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
Assertions.assertThat(commentRepository.findById(reCommend4Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
clear();
//when
mockMvc.perform(
delete("/comment/"+commentId)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
clear();
//then
LongStream.rangeClosed(commentId, reCommend4Id).forEach(id ->
assertThat(assertThrows(CommentException.class, () -> commentRepository.findById(id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).getExceptionType()).isEqualTo(CommentExceptionType.NOT_POUND_COMMENT)
);
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ง ์์ ๊ฒฝ์ฐ
// ๋ด์ฉ๋ง ์ญ์ , DB์์๋ ์ญ์ X
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
//when
mockMvc.perform(
delete("/comment/"+reCommend1Id)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
clear();
//then
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
Assertions.assertThat(commentRepository.findById(reCommend1Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isFalse();
Assertions.assertThat(commentRepository.findById(reCommend1Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ด์๊ณ , ๋๋๊ธ๋ค๋ ๋ชจ๋ ์ญ์ ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ๋ฅผ ํฌํจํ ๋ชจ๋ ๋๋๊ธ์ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์์๋ ์ง์
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ_๋ชจ๋ _๋๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
commentService.remove(reCommend2Id);
clear();
commentService.remove(commentId);
clear();
commentService.remove(reCommend3Id);
clear();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getChildList().size()).isEqualTo(3);
//when
mockMvc.perform(
delete("/comment/"+reCommend1Id)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
//then
LongStream.rangeClosed(commentId, reCommend3Id).forEach(id ->
assertThat(assertThrows(CommentException.class, () -> commentRepository.findById(id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).getExceptionType()).isEqualTo(CommentExceptionType.NOT_POUND_COMMENT)
);
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ด์๊ณ , ๋ค๋ฅธ ๋๋๊ธ์ด ์์ง ์ญ์ ๋์ง ์๊ณ ๋จ์์๋ ๊ฒฝ์ฐ
//ํด๋น ๋๋๊ธ๋ง ์ญ์ , ๊ทธ๋ฌ๋ DB์์ ์ญ์ ๋์ง๋ ์๊ณ , ํ๋ฉด์์๋ "์ญ์ ๋ ๋๊ธ์
๋๋ค"๋ผ๊ณ ํ์
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ_๋ค๋ฅธ_๋๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
commentService.remove(reCommend3Id);
commentService.remove(commentId);
clear();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getChildList().size()).isEqualTo(3);
//when
mockMvc.perform(
delete("/comment/"+reCommend2Id)
.header("Authorization", "Bearer "+ getAccessToken()))
.andExpect(status().isOk());
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
//then
Assertions.assertThat(commentRepository.findById(reCommend2Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT))).isNotNull();
Assertions.assertThat(commentRepository.findById(reCommend2Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).isRemoved()).isTrue();
Assertions.assertThat(commentRepository.findById(reCommend1Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getId()).isNotNull();
Assertions.assertThat(commentRepository.findById(reCommend3Id).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getId()).isNotNull();
Assertions.assertThat(commentRepository.findById(commentId).orElseThrow(()-> new CommentException(CommentExceptionType.NOT_POUND_COMMENT)).getId()).isNotNull();
}
}
์ด๋ฒ์๋ ๊ธ์ด ์ข ์งง๋ค์.
์ฌ์ค ์ด๊ฑด ๋๊ธ ์๋น์ค๋ฅผ ๊ตฌํํ์ ๋ ๊ฐ์ด ํ์ด์ผ ํ๋๋ฐ,, ์ค์์์ต๋๋ค.
์๋ฌดํผ ์ด๋ ๊ฒ ํด์ ๊ธฐ๋ณธ์ ์ธ ๊ฒ์ํ API๋ฅผ ๋ง๋ค์ด ๋ณด์์ต๋๋ค.
์ด์ ์ฌ๊ธฐ์ ์ด๋ฐ์ ๋ฐ ๊ธฐ๋ฅ๋ค์ ๊ณต๋ถํ๋ฉฐ ์ถ๊ฐํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ ์ฒด ์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
https://github.com/ShinDongHun1/SpringBoot-Board-API