Programming/TDD Project

Pull Request 012 - 자유 게시글 등록 기능 컨트롤러 계층 TDD로 구현하기

JKROH 2023. 11. 20. 21:28
반응형

 이번에는 컨트롤러에 대한 테스트를 작성하고 기능을 구현해보려 한다. 앞서 Service계층의 상위 기능의 테스트를 작성하겠다고 했는데, 다시 생각해도 이미 완성된 테스트와 기능을 가져다 쓰기만 하는데 굳이 테스트가 필요할까 싶어서 만들지 않았다. 아무튼 컨트롤러 계층의 테스트 작성을 시작해보자.

 

 컨트롤러 계층의 테스트를 하기에 앞서 우리는 한 가지 선택을 해야한다. @SpringBootTest를 사용할 것인가, @WebMvcTest를 사용할 것인가. 둘의 차이는 여러 글들을 통해 확인할 수 있으니 확인해보길 바란다. 참고 할만한 글들을 하단에 링크로 남긴다. 아주 간단히 요약하자면, 통합 테스트를 진행하고 싶으면 @SpringBootTest를 사용하는 것이, 단위 테스트를 진행하고 싶으면 @WebMvcTest를 사용하는 것이 낫다.

 

 아무튼 이번에는 @WebMvcTest를 사용한다. 단위 테스트를 통해 먼저 컨트롤러 계층의 기능을 구현하고 추후 통합 테스트를 따로 진행하는 편이 더 낫다고 생각해서 이렇게 결정했다.

 

 게시글 등록을 위한 API 설계는 다음과 같다.

  • HTTP 메서드는 Post메서드를 사용한다.
  • urn은 "/free-board"를 사용한다.
  • FreeBoardController#postFreeBoard(FreeBoardDto dto); 메서드에서 Post DTO를 받아와 FreeBoard Serivce에 넘겨주고 Response DTO를 받아온다.
  • 받아온 Response DTO를 ResponseEntity객체로 변환해 반환한다. 이 때, HttpStatus는 created로 설정한다.

 먼저 컨트롤러 클래스를 만들고, 해당 메서드를 정의해보자.

@RestController
@RequestMapping("/free-board")
public class FreeBoardController {

    private final FreeBoardService freeBoardService;

    public FreeBoardController(FreeBoardService freeBoardService) {
        this.freeBoardService = freeBoardService;
    }

    @PostMapping
    public ResponseEntity postFreeBoard(@RequestBody FreeBoardDto.Post dto){
        return null;
    }
}

 

 테스트 클래스도 만들어보자.

@WebMvcTest(FreeBoardController.class)
class FreeBoardControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private FreeBoardServiceImpl freeBoardService;

    @InjectMocks
    private FreeBoardController freeBoardController;

    @Test
    void postFreeBoardTest(){
        
    }
}

 

@WebMvcTest 애너테이션을 사용했고, 테스트를 위한 MockMvc 객체를 주입받는다. Controller 생성에 필요한 Mock 객체를 만들고 주입해줬다. 테스트 메서드도 만들었다.

    @Test
    void postFreeBoardTest(){
        FreeBoardDto.Post testDto = new FreeBoardDto.Post();
        testDto.setTitle("title");
        testDto.setContent("content");

        assertThat(freeBoardController.postFreeBoard(testDto)).isNull();
    }

  

 일단은 간단하게 테스트 코드를 작성했다. 당연히 통과할 것이다. postFreeBoard() 메서드는 null을 반환한다. 이제, 우리가 원하는 ResponseEntity를 반환하는지 테스트 할 수 있게 테스트 코드를 수정해보자. 일단 앞서 정의했던 Service의 postFreeBoard를 통해 Response DTO를 받아야 한다.

    @Test
    void postFreeBoardTest(){
        // given
        String testTitle = "title";
        String testContent = "content";

        FreeBoardDto.Post testPost = new FreeBoardDto.Post();
        testPost.setTitle(testTitle);
        testPost.setContent(testContent);

        FreeBoardDto.Response testResponse = FreeBoardDto.Response
                .builder()
                .title(testTitle)
                .content(testContent)
                .build();

        given(freeBoardService.postFreeBoard(any())).willReturn(testResponse);

        assertThat(freeBoardController.postFreeBoard(testPost)).isNull();
    }

 

 이제 MockMvc를 통해 Post 요청을 수행시켜야한다.

@WebMvcTest(FreeBoardController.class)
class FreeBoardControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Gson gson;

    @MockBean
    private FreeBoardServiceImpl freeBoardService;

    @InjectMocks
    private FreeBoardController freeBoardController;

    @Test
    void postFreeBoardTest() throws Exception {
        // given
        String testTitle = "title";
        String testContent = "content";

        FreeBoardDto.Post testPost = new FreeBoardDto.Post();
        testPost.setTitle(testTitle);
        testPost.setContent(testContent);

        FreeBoardDto.Response testResponse = FreeBoardDto.Response
                .builder()
                .title(testTitle)
                .content(testContent)
                .build();

        String content = gson.toJson(testPost);

        given(freeBoardService.postFreeBoard(any())).willReturn(testResponse);

        // when
        ResultActions actions =
                mockMvc.perform(
                        post("/free-board")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                );
        assertThat(freeBoardController.postFreeBoard(testPost)).isNull();
    }

 

 mockMvc를 사용해 Post 요청을 수행한다. 클라이언트와 통신할 때, JSON 타입의 데이터를 주고받는 상황을 상정하기 때문에 테스트를 수행할 때도 JSON 타입의 문자열을 만들어줘야 한다. 이 과정을 gson 라이브러리를 사용해 수행했다. 귀찮게 일일이 JSON 타입으로 문자열을 만드는 것보다 잘 만들어진 라이브러리를 사용하는 것이 더 낫다. gson 라이브러리를 사용하고 싶다면, build.gradle 파일에 다음 의존성을 추가하자.

implementation 'com.google.code.gson:gson:2.10.1'
  • accept() : 클라이언트 쪽에서 리턴 받을 응답 데이터 타입으로 JSON 타입을 설정한다.
  • contentType() : 서버 쪽에서 처리 가능한 Content Type으로 JSON 타입을 설정한다.

 마지막으로 이렇게 수행한 예상 결과를 만들어보자.

    @Test
    void postFreeBoardTest() throws Exception {
        // given
        String testTitle = "title";
        String testContent = "content";

        FreeBoardDto.Post testPost = new FreeBoardDto.Post();
        testPost.setTitle(testTitle);
        testPost.setContent(testContent);

        FreeBoardDto.Response testResponse = FreeBoardDto.Response
                .builder()
                .title(testTitle)
                .content(testContent)
                .build();

        String content = gson.toJson(testPost);

        given(freeBoardService.postFreeBoard(any())).willReturn(testResponse);

        // when // then
        mockMvc.perform(
                        post("/free-board")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                ).andExpect(status().isCreated())
                .andExpect(jsonPath("$.title").value(testPost.getTitle()))
                .andExpect(jsonPath("$.content").value(testPost.getContent()));
    }

 

 해당 테스트를 수행하면, 당연히 fail이 나온다. Controller는 null을 반환한다. 이제 postFreeBoard()메서드를 구현해보자.

  • andExpect(status().isCreated()) : Response의 Http Status가 Created일 것임을 기대한다.
  • andExpect(jsonPath("$.key").value(expected) : JSON 형식의 Response Body의 각 프로퍼티 중, 응답으로 전달받는 key 값이 request body로 전송한 값(expected)과 일치하는지 검증한다.
    @PostMapping
    public ResponseEntity<FreeBoardDto.Response> postFreeBoard(@RequestBody FreeBoardDto.Post dto) {
        FreeBoardDto.Response responseDto = freeBoardService.postFreeBoard(dto);
        return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
    }

 

 상당히 간단하다. Service에서 모든 로직을 처리하고 Response DTO로 반환까지 해준다. 해당 객체로 Response Entity를 만들어 반환해주기만 하면 된다. 이제 방금 수행한 테스트를 다시 수행하면 테스트가 통과한다.

 

전체 코드는 링크에서 확인할 수 있다.

 

 

- 참고링크

https://developer-pi.tistory.com/310

 

SpringBootTest vs WebMvcTest

@SpringBootTest 는 기본 구성 클래스를 찾고 Spring Application Context를 시작하도록 명령한다. @SpringBootTest public class SmokeTest { @Autowired private HomeController controller; @Test public void contextLoads() throws Exception { ass

developer-pi.tistory.com

https://www.appsdeveloperblog.com/difference-between-springboottest-and-webmvctest/

 

Difference Between @SpringBootTest and @WebMvcTest - Apps Developer Blog

Learn the differences between the @SpringBootTest annotation and the @WebMvcTest annotation in this beginner-friendly tutorial. Find out when to use each one.

www.appsdeveloperblog.com

https://www.javaguides.net/2022/03/springboottest-vs-webmvctest.html

 

@SpringBootTest vs @WebMvcTest

In this quick article, we will understand what is @SpringBootTest, @WebMvcTest annotations and what is difference between them.

www.javaguides.net

https://stackoverflow.com/questions/39865596/difference-between-using-mockmvc-with-springboottest-and-using-webmvctest

 

Difference between using MockMvc with SpringBootTest and Using WebMvcTest

I am new to Spring Boot and am trying to understand how testing works in SpringBoot. I am a bit confused about what is the difference between the following two code snippets: Code snippet 1: @RunW...

stackoverflow.com

 

반응형