ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] REST API 만들기(9) - Controller 구현 및 테스트(Junit4)
    Spring Boot/2.4.x - REST API 만들기 2020. 5. 10. 16:32
    반응형

    스프링 프레임워크를 이용한 REST 서비스 생성 방법은 크게 두 가지로 나눌 수 있습니다. MVC의 ModelAndView를 사용하는 방법과 HTTPMessageConverter를 사용하는 방법입니다. HTTPMessageConverter를 사용해서 Controller를 구현하겠습니다.

     

    HTTPMessageConverter

    HTTPMessageConverter는 자바 객체와 HTTP 요청/응답 몸체(Body)를 변환하는 역할을 합니다. HTTP 요청과 응답이 문자열 기반으로 이루어진다는 것으로 이는 클라이언트와 서버가 문자열로 서로 통신을 한다는 것 입니다. 스프링은 이런 문자열을 자바 객체로 변환해주는 기능을 제공하는데, 그 기능을 하는 것이 HTTPMessageConverter입니다.

    스프링에서는 HTTPMessageConverter를 사용하기 위해서 @RequestBody 어노테이션과 @ResponseBody 어노테이션을 제공합니다. @RequestBody 어노테이션은 HTTP 요청 몸체를 자바 객체로 변환하는데 사용되고, @ResponseBody 어노테이션은 자바 객체를 HTTP 응답 몸체를 변환하는데 사용됩니다.

    @RestController은 @Controller와 @ResponseBody의 조합으로 클래스에 @RestController을 선언하면 메서드에 @ResponseBody를 선언하지 않아도 단순히 객체만을 반환하고 객체 데이터는 JSON 또는 XML 형식으로 HTTP 응답에 담아서 전송할 수 있습니다.

     

    1. Controller 구현

    컨트롤러를 구현 시에 게시글 목록 조회, 상세 조회는 GET 방식, 게시글 등록은 POST 방식, 게시글 수정은 PUT 방식, 게시글 삭제는 DELETE 방식으로 구분하여 URI에 자원을 표현할 수 있도록 구현하세요.

    BoardController.java

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    package com.api.board.controller;
     
    import java.util.List;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestController;
     
    import com.api.board.domain.Board;
    import com.api.board.service.BoardService;
     
    @RequestMapping("/board")
    @RestController
    public class BoardController {
     
        @Autowired
        private BoardService boardService;
     
        /** 게시글 목록 조회 */
        @GetMapping
        public List<Board> getBoardList() throws Exception {
            return boardService.getBoardList();
        }
     
        /** 게시글 상세 조회 */
        @GetMapping("/{board_seq}")
        public Board getBoardDetail(@PathVariable("board_seq"int board_seq) throws Exception {
            return boardService.getBoardDetail(board_seq);
        }
     
        /** 게시글 등록  */
        @ResponseStatus(value = HttpStatus.CREATED)
        @PostMapping
        public Board insertBoard(@RequestBody Board board) throws Exception {
     
            boardService.insertBoard(board);
     
            int boardSeq = board.getBoard_seq();
     
            Board boardDetail = boardService.getBoardDetail(boardSeq);
     
            return boardDetail;
        }
     
        /** 게시글 수정 */
        @ResponseStatus(value = HttpStatus.OK)
        @PutMapping("/{board_seq}")
        public Board updateBoard(@PathVariable("board_seq"int board_seq, @RequestBody Board board) throws Exception {
     
            boardService.updateBoard(board);
     
            Board boardDetail = boardService.getBoardDetail(board_seq);
     
            return boardDetail;
        }
     
        /** 게시글 삭제  */
        @ResponseStatus(value = HttpStatus.OK)
        @DeleteMapping("/{board_seq}")
        public Board deleteBoard(@PathVariable("board_seq"int board_seq) throws Exception {
     
            boardService.deleteBoard(board_seq);
     
            Board deleteBoard = new Board();
            deleteBoard.setBoard_seq(board_seq);
     
            return deleteBoard;
        }
    }
    cs

     

    2. WebMvcConfig.java 수정 

    각각의 Controller를 Bean 형태로 선언해서 등록할 수 있지만, @ComponentScan 어노테이션을 이용하여 스캔 대상이 되는 기본 패키지를 com.api.board.controller로 선언하고, 필터(@Filter)를 사용해서 Controller 클래스만 스캔하세요.

    WebMvcConfig.java

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.api.board.config;
     
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.ComponentScan.Filter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import com.api.board.interceptor.BoardInterceptor;
     
    @ComponentScan(basePackages = { "com.api.board.controller" }, useDefaultFilters = false, includeFilters = { @Filter(Controller.class) })
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
     
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new BoardInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/sample/**");
        }
    }
    cs

     

    3. Controller 테스트

    Mapper나 Service 클래스와는 달리 Controller 클래스는 WAS 상에서 작동하기 때문에 서버를 구동한 후, 직접 주소를 호출하여 테스트해야 합니다. 이런한 불편함을 해소하기 위해서 Controller 클래스를 테스트할 때는 JUnit 상에서 MockMvc를 이용해서 테스트할 수 있습니다. MockMvc는 별도의 서버없이 모조폼(Mock)을 만들어서 Controller를 테스트할 수 있도록 도와줍니다. 

    MockMvc를 이용한 테스트를 위해서는 테스트 클래스 상단에 테스트할 대상이 웹 애플리케이션임을 알려주는 @WebAppConfiguration 어노테이션을 추가하고, 테스트 케이스가 실행되기 전에 MockMvc를 생성해야하므로, setUp() 메소드에 @Before 어노테이션을 추가한 후 MockMvcBuilders 클래스를 이용해 MockMvc를 생성하세요. 또한, 한글 처리를 위해 .addFilters(new CharacterEncodingFilter("UTF-8", true))와 테스트 결과를 콘솔에 출력하기 위해 .alwaysDo(print())도 추가하세요.

    BoardControllerTest.java

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    package com.api.board.controller;
     
    import static org.junit.Assert.assertEquals;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
     
    import java.io.IOException;
     
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.json.JsonParseException;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.annotation.Rollback;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.filter.CharacterEncodingFilter;
     
    import com.api.board.domain.Board;
    import com.api.board.service.BoardServiceTest;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
     
    @WebAppConfiguration
    @Transactional
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class BoardControllerTest {
     
        Logger logger = LoggerFactory.getLogger(BoardServiceTest.class);
     
        private MockMvc mockMvc;
     
        @Autowired
        WebApplicationContext webApplicationContext;
     
        public String mapToJson(Object obj) throws JsonProcessingException {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(obj);
        }
     
        public <T> T mapFromJson(String json, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(json, clazz);
        }
     
        @Before
        public void setUp() {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                                     .addFilters(new CharacterEncodingFilter("UTF-8"true))
                                     .alwaysDo(print())
                                     .build();
        }
        
        int boardSeq = 0;
        
        /** 게시글 목록 조회 시 응답 값이 200이면 테스트 통과 */
        @Test
        public void testGetBoardList() {
     
            try {
                
                MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/board")
                                             .accept(MediaType.APPLICATION_JSON_VALUE))
                                            .andReturn();
                    
                assertEquals(200, mvcResult.getResponse().getStatus());
     
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        /** 게시글 상세 조회 시 응답 값이 200이면 테스트 통과 */
        @Test
        public void testGetBoardDetail() {
            
            try {
                
                if (boardSeq != 0) {
                    
                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/board/" + boardSeq)
                                                 .accept(MediaType.APPLICATION_JSON_VALUE))
                                                 .andReturn();
     
                    assertEquals(200, mvcResult.getResponse().getStatus());
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        /** 게시글 등록 시 응답 값이 201이면 테스트 통과 */
        @Rollback(true)
        @Test
        public void testInsertBoard() throws Exception {
     
            Board insertBoard = new Board();
            insertBoard.setBoard_writer("게시글 작성자 등록");
            insertBoard.setBoard_subject("게시글 제목 등록");
            insertBoard.setBoard_content("게시글 내용 등록");
     
            MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/board")
                                         .contentType(MediaType.APPLICATION_JSON_VALUE)
                                         .content(this.mapToJson(insertBoard)))
                                         .andReturn();
     
            assertEquals(201, mvcResult.getResponse().getStatus());
        }
     
        /** 게시글 수정 시 응답 값이 200이면 테스트 통과 */
        @Rollback(true)
        @Test
        public void testUpdateBoard() {
            
            try {
                
                if (boardSeq != 0) {
                     
                    Board updateBoard = new Board();
                    updateBoard.setBoard_seq(boardSeq);
                    updateBoard.setBoard_subject("게시글 제목 수정");
                    updateBoard.setBoard_content("게시글 내용 수정");
             
                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/board/" + boardSeq)
                                                 .contentType(MediaType.APPLICATION_JSON_VALUE)
                                                 .content(this.mapToJson(updateBoard)))
                                                 .andReturn();
             
                    int status = mvcResult.getResponse()
                                          .getStatus();
                 
                    assertEquals(200, status);
                } 
                
            } catch (Exception e) {
                e.printStackTrace();
            }     
        }
     
        /** 게시글 삭제 시 응답 값이 200이면 테스트 통과 */
        @Rollback(true)
        @Test
        public void testDeleteBoard() { 
     
            try {
                
                if (boardSeq != 0) {
                    
                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.delete("/board/" + boardSeq))
                                                 .andReturn();
             
                    int status = mvcResult.getResponse()
                                          .getStatus();
     
                    assertEquals(200, status);
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    cs

     

    소스 코드는 Github Repository - https://github.com/tychejin1218/api-board_v1 (branch : section09) 를 참조하세요.
    Github에서 프로젝트 가져오기 - 
    https://tychejin.tistory.com/33

    반응형

    댓글

Designed by Tistory.