@Api(tags = "게시판 CRUD API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/posts")
public class PostApiController {
private final PostService postService;
/**
* [API] 게시글 등록
*
* @return ResponseEntity<Object>: 생성된 게시글 번호 및 응답 코드 반환
*/
@Operation(summary = "게시글 등록", description = "게시글을 등록 합니다.\n + X-USERID는 3~10자 사이여야 합니다.")
@ApiResponse(code = 201, message ="INSERT SUCCESS")
@PostMapping
public CustomApiResponse<Long> addPost(@Valid @RequestBody CreatePostDto createPostDto, @RequestHeader(name = "X-USERID") @Size(min=3,max = 10) String userId) {
Long result = postService.addPost(createPostDto, userId);
return new CustomApiResponse<>(result,SuccessCode.INSERT_SUCCESS);
}
/**
* [API] 게시글 단건 조회
*
* @return ResponseEntity<Object>: 조회한 게시글 결과 및 응답 코드 반환
*/
@Operation(summary = "게시글 단건 조회", description = "입력한 게시글 번호에 대한 게시글 정보를 조회 합니다.")
@ApiResponse(code = 200, message ="INSERT SUCCESS", response = CustomApiResponse.class)
@GetMapping("/{postId}")
public CustomApiResponse<ResponsePostDto> getPost(@PathVariable Long postId) {
ResponsePostDto result = postService.getPost(postId);
return new CustomApiResponse<>(result,SuccessCode.SELECT_SUCCESS);
}
/**
* [API] 게시글 수정
*
* @return RResponseEntity<Object>: 수정된 게시글 번호 및 응답 코드 반환
*/
@Operation(summary = "게시글 수정", description = "해당 게시글에 대한 정보를 수정합니다.\n + X-USERID는 3~10자 사이여야 합니다.")
@ApiResponse(code = 200, message ="INSERT SUCCESS")
@PatchMapping
public CustomApiResponse<Long> modifyPost(@Valid @RequestBody UpdatePostDto updatePostDto, @RequestHeader(name = "X-USERID") @Size(min=3,max = 10) String userId) {
if (updatePostDto.getId() == null) {
throw new IllegalArgumentException("게시글 번호는 필수 입니다.");
}
Long result = postService.modifyPost(updatePostDto, userId);
return new CustomApiResponse<>(result,SuccessCode.UPDATE_SUCCESS);
}
/**
* [API] 게시글 삭제
*
* @return ResponseEntity<ApiResponse<PostDto>>: 삭제된 게시글 번호 및 응답 코드 반환
*/
@Operation(summary = "게시글 삭제", description = "해당 게시글을 삭제합니다.\n + X-USERID는 3~10자 사이여야 합니다.")
@ApiResponse(code = 204, message ="INSERT SUCCESS")
@DeleteMapping("{postId}")
public CustomApiResponse<Long> deletePost(@PathVariable Long postId, @RequestHeader(name = "X-USERID") @Size(min=3,max = 10) String userId) {
postService.deletePost(postId, userId);
return new CustomApiResponse<>(postId,SuccessCode.DELETE_SUCCESS);
}
/**
* [API] 게시글 목록 조회
*
* @return ResponseEntity<Object>: 삭제된 게시글 번호 및 응답 코드 반환
*/
@Operation(summary = "게시글 목록 조회", description = "해당되는 카테고리의 게시글 목록을 조회합니다. - 빈 값일시 모든 게시글을 기준으로 조회")
@ApiResponse(code = 200, message ="INSERT SUCCESS")
@GetMapping
public CustomApiResponse<PageResultDto<ResponsePostListDto>> getPostList(
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "5") int size,
@RequestParam(name = "keyword", required = false) String keyword
) {
PageRequestDto pageRequestDto = new PageRequestDto(page,size,keyword);
PageResultDto<ResponsePostListDto> result = postService.getPostList(pageRequestDto);
return new CustomApiResponse<>(result,SuccessCode.SELECT_SUCCESS);
}
}
@WebMvcTest을 사용했으며 MockMvc를 사용하여 API 엔드포인트의 용청과 응답을 테스트를 진행했다.
Error "jpa metamodel must not be empty"
@WebMvcTest시에는controller
,SpringMV
레벨의 컴포넌트만 구성되는데 JPA-Auditing 관련 빈이 등록이 안된 상태로 Application 에 있던 @EnableJpaAuditing이 수행되면서 발생하는 에러
@MockBean(JpaMetamodelMappingContext.class)을 Mock 빈을 주입하여 해결
@WebMvcTest(PostApiController.class)
@MockBean(JpaMetamodelMappingContext.class)
class PostApiControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
PostService postService;
@Autowired
ObjectMapper objectMapper;
@Nested
@DisplayName("게시글 등록")
class addPostControllerTest {
@DisplayName("성공")
@Test
void success() throws Exception {
//given
CreatePostDto postDto = CreatePostDto.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글내용")
.build();
String xUserId = "user1";
Post post = Post.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글내용")
.author(xUserId)
.build();
when(postService.addPost(any(CreatePostDto.class), anyString())).thenReturn(post.getId());
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto))
.header("X-USERID",xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultMsg").value("INSERT SUCCESS"));
}
@DisplayName("실패 - X-USERID 값 null")
@Test
void xUserIdIsNull() throws Exception {
//given
CreatePostDto postDto = CreatePostDto.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글내용")
.build();
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("X-USERID 값은 필수 입니다."));
}
@DisplayName("실패 - X-USERID 유효성")
@Test
void xUserIdInvalid() throws Exception{
//given
CreatePostDto postDto = CreatePostDto.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글내용")
.build();
String xUserId = "uuser1asduwqrqrq";
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto))
.header("X-USERID", xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("X-UERID는 3자에서 10자 사이여야 합니다."));
}
@DisplayName("실패 - 게시글 카테고리 null")
@Test
void postNameIsNull() throws Exception{
//given
CreatePostDto postDto = CreatePostDto.builder()
.title("게시글 제목")
.content("게시글내용")
.build();
String xUserId = "user1";
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto))
.header("X-USERID", xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("게시글의 카테고리는 필수 값 입니다."));
}
@DisplayName("실패 - 게시글 제목 길이")
@Test
void postTitleIsEmpty() throws Exception{
//given
String title = "게시글제목";
title = title.repeat(21);
CreatePostDto postDto = CreatePostDto.builder()
.name("SpringBoot")
.title(title)
.content("게시글내용")
.build();
String xUserId = "user1";
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto))
.header("X-USERID", xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("게시글 제목은 1~100자 사이로 작성해 주세요."));
}
@DisplayName("실패 - 게시글 제목 길이")
@Test
void postTitleIsNull() throws Exception{
//given
CreatePostDto postDto = CreatePostDto.builder()
.name("SpringBoot")
.content("게시글내용")
.build();
String xUserId = "user1";
//when
//then
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postDto))
.header("X-USERID", xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("게시글의 제목은 필수 값 입니다."));
}
}
@Nested
@DisplayName("게시글 수정")
class modifyPostControllerTest {
@DisplayName("성공")
@Test
void success() throws Exception {
//given
String modifiedText = "텍스트 수정";
UpdatePostDto updatePostDto = UpdatePostDto.builder()
.id(1L)
.name("Spring")
.title("타이틀 변경")
.content(modifiedText)
.build();
String xUserId = "user1";
when(postService.modifyPost(any(),anyString()))
.thenReturn(1L);
//when
//then
mockMvc.perform(patch("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatePostDto))
.header("X-USERID",xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultMsg").value("UPDATE SUCCESS"));
}
@DisplayName("실패 - 게시글번호 null")
@Test
void postIdIsNull() throws Exception {
//given
UpdatePostDto updatePostDto = UpdatePostDto.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글 내용")
.build();
//when
//then
mockMvc.perform(patch("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatePostDto))
.header("X-USERID", "user2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("게시글 번호는 필수 값 입니다."));
}
@DisplayName("실패 - 작성자 수정자 다름")
@Test
void mismatchXUerId() throws Exception{
//given
Post post = Post.builder()
.name("SpringBoot")
.title("게시글 생성")
.content("게시글 내용")
.author("user1")
.build();
UpdatePostDto updatePostDto = UpdatePostDto.builder()
.id(1L)
.name("SpringBoot")
.title("게시글 생성")
.content("게시글 내용")
.build();
when(postService.modifyPost(any(), anyString()))
.thenThrow(new BusinessExceptionHandler("작성자와 수정자가 다릅니다.", ErrorCode.FORBIDDEN_ERROR));
//when
//then
mockMvc.perform(patch("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatePostDto))
.header("X-USERID", "user2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("작성자와 수정자가 다릅니다."));
}
}
@DisplayName("게시글 삭제")
@Test
void deletePost() throws Exception {
//given
Long postId = 1L;
String xUserId = "user1";
//when
//then
mockMvc.perform(delete("/posts/"+postId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postId))
.header("X-USERID", xUserId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultMsg").value("DELETE SUCCESS"));
}
@DisplayName("게시글 목록 조회")
@Test
void getPostList() throws Exception {
//given
String param = "page=1&size=5&keyword='카테고리'";
//when
//then
mockMvc.perform(get("/posts?"+param)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultMsg").value("SELECT SUCCESS"));
}
}
@Api
@Operation
메서드 레벨에 사용되며, 해당 메서드에 대한 기능 설명
summary
:작업에 대한 간단한 설명을 제공
description
: 작업에 대한 보다 자세한 설명을 제공
@ApiResponse
code
: HTTP 응답 코드를 지정message
: 응답 코드에 대한 설response
응답의 타입을 지정@NotNull
, @Min
, @Max
, @Size
Swagger 문서에서 API테스트 진행시 해당 어노테이션 기능을 지원해준다고한다.
ex)헤더의 X-USERID의 경우에 3~10자를 만족하지 않을경우 Excute가 실행되지 않음, 아래 이미처럼 붉게 표시되는걸 확인할 수 있다.
큰 도움이 되었습니다, 감사합니다.