프로그래밍/Spring

[Spring] REST API 테스트 코드 작성하기

내일배움캠프 Spring 개인과제 (익명 게시판 API구현하기)를 하면서 

TDD 구현 프로세스로 하자고 마음먹고 진행했다. 

 

그래서 기본적인 설계 (유즈케이스, ERD, API 명세)를 작성하였고

컨트롤러 테스트코드 -> 컨트롤러 -> 서비스테스트코드 -> 서비스 , 레포지토리 

순서로 구현을 생각해서 가장 먼저 컨트롤러 테스트 코드를 작성해보았다. 


컨트롤러 테스트 코드 작성하기

컨트롤러 테스트도 그렇고 각 테스트는 단위 테스트로 작성하였다. 

그래서 컨트롤러가 의존하는 컴포넌트(서비스)는 Mokito 프레임워크를 이용해 Mocking하여 진행하였다. 

@Import(AppConfig.class)  //PasswordEncoder Bean 설정 클래스
@WebMvcTest(PostController.class)
@DisplayName("게시글 API 컨트롤러 테스트")
class PostControllerTest {
    private final MockMvc mvc;
    private final ObjectMapper objectMapper;
    private final PasswordEncoder passwordEncoder;

    @MockBean
    private PostService postService;

    PostControllerTest(
            @Autowired MockMvc mvc,
            @Autowired ObjectMapper objectMapper,
            @Autowired PasswordEncoder passwordEncoder) {
        this.mvc = mvc;
        this.objectMapper = objectMapper;
        this.passwordEncoder = passwordEncoder;
    }

 

그리고 게시글 생성, 조회, 수정, 삭제 API 에서 받을 요청,응답 케이스별로

테스트 메소드를 작성해주었다.

 

 

테스트 코드를 작성할 때는 Given When Then 패턴으로 모두 작성하였다.

 

- given(준비): 어떠한 데이터가 준비되었을 때

- when(실행): 어떠한 함수를 실행하면

- then(검증): 어떠한 결과가 나와야 한다.


게시글 생성 API 

- 게시글 생성을 위한 데이터에 대한 유효성검증테스트,

- 생성이 성공할 경우 정상 요청,응답 테스트가 있다. 

 

- 게시글 생성을 위한 데이터에 대한 유효성 검증 테스트

게시글 생성에 필요한 데이터 (제목, 내용, 비밀번호, 내용)이 없거나 특정 길이 이하면

400 응답코드를 내려주고 해당되는 데이터에 대한 오류메시지를 주기로 정하고 이에 맞는 코드를 작성했다.

 

@Test
@DisplayName("[Controller][POST] 게시글 생성 필드 유효성 검증 실패")
void givenPostRequest_whenRequesting_thenFailedValid() throws Exception {
    //given - 유효성 검증에 실패할 요청 데이터 세팅
    PostRequest request = PostRequest.of(
            "",
            "",
            "",
            ""
    );

    //when - 서버에 Post로 생성 API 요청
    ResultActions actions = mvc.perform(
            post("/api/posts")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(
                                    request
                            )

                    )
                    .accept(MediaType.APPLICATION_JSON)
    );

   //Then - 4xx 상태코드와 각 해당하는 필드와 메시지 응답
    actions
            .andDo(print())
            .andExpect(status().is4xxClientError())
            .andExpect(jsonPath("$.name").value("이름은 최소 2자리 이상이어야합니다."))
            .andExpect(jsonPath("$.password").value("비밀번호는 최소 4자리 이상이어야합니다."))
            .andExpect(jsonPath("$.title").value("제목을 입력해주세요."));

}

 

- 생성이 성공할 경우 정상 요청,응답 테스트

게시글 생성에 필요한 정상적인 데이터로 요청을 하였을 때,

생성에 성공하여 정상적인 응답을 줄 때, 201 상태코드와 생성 게시글 정보 반환

 

@Test
@DisplayName("[Controller][POST] 게시글 생성 성공")
void givenPostRequest_whenRequesting_thenSuccess() throws Exception {
    //given
    PostRequest request = PostRequest.of(
            "testName",
            "testPassword",
            "test Title",
            "test Content"
    );
    given(postService.createPost(request)).willReturn(PostResponse.from(request.toEntity(passwordEncoder)));
    //when
    ResultActions actions = mvc.perform(
            post("/api/posts")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request))
                    .accept(MediaType.APPLICATION_JSON)
    );

    actions
            .andDo(print())
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value(request.name()))
            .andExpect(jsonPath("$.title").value(request.title()))
            .andExpect(jsonPath("$.content").value(request.content()));
}

 

 

게시글 목록 조회 API

- 게시글 목록 조회를 성공했을 때, 정상응답

@Test
@DisplayName("[Controller][GET] 게시글 목록 조회 성공")
void givenNothing_whenRequesting_thenSuccess() throws Exception {
    //given
    ArrayList<PostResponse> response = new ArrayList<>();
    response.add(new PostResponse(1L, "testName1", "testTitle1", "testContent1", LocalDateTime.now()));
    response.add(new PostResponse(2L, "testName2", "testTitle2", "testContent2", LocalDateTime.now()));
    response.add(new PostResponse(3L, "testName3", "testTitle3", "testContent3", LocalDateTime.now()));

    when(postService.getPosts()).thenReturn(response);
    //when
    ResultActions actions = mvc.perform(
            get("/api/posts")
    );

    actions
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("testName1")))
            .andExpect(content().string(containsString("testName2")))
            .andExpect(content().string(containsString("testName3")));
}

 

- 게시글 목록이 없는 경우, 빈 List 데이터 정상 반환

@Test
@DisplayName("[Controller][GET] 게시글 목록 없는 경우 조회")
void givenNothing_whenRequesting_thenNoContentSuccess() throws Exception {
    //given
    when(postService.getPosts()).thenReturn(List.of());
    //when
    ResultActions actions = mvc.perform(
            get("/api/posts")
    );

    actions
            .andDo(print())
            .andExpect(status().isOk());
}

 

게시글 상세조회 API

- 게시글 상세조회 성공 시 정상 응답 반환

@Test
@DisplayName("[Controller][GET] 게시글 상세 조회 성공")
void givenPostId_whenRequesting_thenReturnPosts() throws Exception {
    //given
    Long postId = 1L;
    PostRequest request = PostRequest.of(
            "testName",
            "testPassword",
            "test Title",
            "test Content"
    );

    when(postService.getPost(postId)).thenReturn(
            PostResponse.from(request.toEntity(passwordEncoder))
    );

    //when
    ResultActions actions = mvc.perform(
            get("/api/posts/" + postId)
    );

   //Then
    actions
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value(request.name()))
            .andExpect(jsonPath("$.title").value(request.title()))
            .andExpect(jsonPath("$.content").value(request.content()));
}

 

- 없는 게시글 ID 로 조회 요청 시 404 상태코드로 응답

@Test
@DisplayName("[Controller][GET] 없는 게시글 번호로 상세 조회 시 상태코드 404 반환")
void givenPostId_whenRequesting_thenReturnThrow() throws Exception {
    //given
    Long postId = 1L;
    //when
    when(postService.getPost(postId)).thenThrow(new NoSuchElementException("조회할 게시글이 없습니다."));

    ResultActions actions = mvc.perform(
            get("/api/posts/" + postId)
    );

    //then
    actions
            .andDo(print())
            .andExpect(status().is4xxClientError())
            .andExpect(jsonPath("$.msg").value("조회할 게시글이 없습니다."));
}

 

게시글 수정 API

- 게시글 수정 요청 시 정상 응답

@Test
@DisplayName("[Controller][PUT] 게시글 수정 요청 시 정상 응답")
void givenUpdatePostInfo_whenRequesting_thenUpdate() throws Exception {
    //Given
    Long postId = 1L;
    PostRequest request = PostRequest.of(
            "updatedName",
            "testPassword",
            "updatedTitle",
            "updatedContent"
    );

    Post updatedPost = Post.of("updatedName", "testPassword", "updatedTitle", "updatedContent");
    when(postService.updatePost(postId, request)).thenReturn(
            PostResponse.from(updatedPost)
    );

    //When
    ResultActions actions = mvc.perform(
            put("/api/posts/" + postId)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request))
    );

    //Then
    actions
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value(request.name()))
            .andExpect(jsonPath("$.title").value(request.title()))
            .andExpect(jsonPath("$.content").value(request.content()));
}

 

- 없는 게시글 ID 로 수정 요청 시 404 상태코드 반환

@Test
@DisplayName("[Controller][PUT] 게시글 수정 요청 시 게시글이 없는 경우  상태코드 404 반환")
void givenUpdatePostInfo_whenRequesting_thenThrowException() throws Exception {
    //Given
    Long postId = 1L;
    PostRequest request = PostRequest.of(
            "updatedName",
            "invalidPassword",
            "updatedTitle",
            "updatedContent"
    );
    when(postService.updatePost(postId, request)).thenThrow(new NoSuchElementException("조회할 게시글이 없습니다."));

    //When
    ResultActions actions = mvc.perform(
            put("/api/posts/" + postId)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request))
    );

    //Then
    actions
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.msg").value("조회할 게시글이 없습니다."));
}

 

- 게시글 수정 요청 시 비밀번호가 다른 경우 403 상태코드 및 에러 메세지 반환 

@Test
@DisplayName("[Controller][PUT] 게시글 수정 요청 시 비밀번호 다를 경우 상태코드 403 반환")
void givenUpdatePostInfoWithInvalidPassword_whenRequesting_thenThrowException() throws Exception {
    //Given
    Long postId = 1L;
    PostRequest request = PostRequest.of(
            "updatedName",
            "invalidPassword",
            "updatedTitle",
            "updatedContent"
    );
    when(postService.updatePost(postId, request)).thenThrow(new InvalidPasswordException());

    //When
    ResultActions actions = mvc.perform(
            put("/api/posts/" + postId)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request))
    );

    //Then
    actions
            .andExpect(status().isForbidden())
            .andExpect(jsonPath("$.msg").value("비밀번호가 일치하지 않습니다."));
}

 

게시글 삭제 API 

- 게시글 삭제 요청 정상 204 상태코드 응답

@Test
@DisplayName("[Controller][DELETE] 게시글 삭제 요청 정상 호출")
void givenPostIdAndPassword_whenRequesting_thenSuccessNoContent() throws Exception {
    //Given
    Long postId = 1L;
    String password = "testPassword";


    //When
    ResultActions actions = mvc.perform(
            delete("/api/posts/" + postId)
                    .header("password", password)
    );

    //Then
    actions
            .andExpect(status().isNoContent());
}

 - 게시글 삭제 요청 시 비밀번호가 없는 경우 400 상태코드 및 메세지 반환

@Test
@DisplayName("[Controller][DELETE] 게시글 삭제 요청 시 비밀번호가 없는 경우 상태코드 400 반환")
void givenPostIdAndNoPassword_whenRequesting_thenThrowException() throws Exception {
    //Given
    Long postId = 1L;

    //When
    ResultActions actions = mvc.perform(
            delete("/api/posts/" + postId)
    );
    //Then
    actions
            .andExpect(status().isBadRequest());
}

 

- 게시글 삭제 요청 시 비밀번호가 다를 경우 403 상태코드 및 메세지 반환

@Test
@DisplayName("[Controller][DELETE] 게시글 삭제 요청 시 비밀번호 다를 경우 상태코드 403 반환")
void givenPostIdAndInvalidPassword_whenRequesting_thenThrowException() throws Exception {
    //Given
    Long postId = 1L;
    String password = "invalidPassword";

    //When
    doThrow(InvalidPasswordException.class).when(postService).deletePost(postId, password);

    ResultActions actions = mvc.perform(
            delete("/api/posts/" + postId)
                    .header("password", password)
    );

    //Then
    actions
            .andExpect(status().isForbidden());
}
728x90