RestApi ๋งŒ๋“ค๊ธฐ - ๊ฐ์ข… ๋ฌธ์„œ ์กฐ๊ฐ ์ƒ์„ฑํ•˜๊ธฐ(6)

๋ฐ˜์‘ํ˜•

์˜ค๋žœ๋งŒ์— ์ž‘์„ฑํ•˜๋Š”๊ฒƒ ๊ฐ™๋‹ค.
์•„๋ฌดํŠผ ๋‹ค์‹œ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์–ด์„œ ๊ธฐ์˜๋‹ค.!

restAPI docs๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” 

<dependency>
   <groupId>org.springframework.restdocs</groupId>
   <artifactId>spring-restdocs-mockmvc</artifactId>
   <scope>test</scope>
</dependency>

์ด ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

 

RestApi ๋งŒ๋“ค๊ธฐ - spring-rest-docs (5)

์ž ๋“œ๋””์–ด rest-docs๋ฅผ ์ ์šฉํ• ๋•Œ๊ฐ€ ์™”๋‹ค. Spring REST Docs Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test. docs.spring.io..

b-programmer.tistory.com

์ด๊ธ€์—์„œ spring-rest-docs๋ฅผ ๋งŒ๋“œ๋Š”๊ฒƒ์„ ์‹ค์Šตํ–ˆ๋‹ค.
์ด๋ฒˆ์—๋Š” spring-rest-docs์—์„œ ์ œ๊ณต๋˜์ง€ ์•Š์€ ๋ฌธ์„œ ์กฐ๊ฐ์„ ์ƒ์„ฑํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.
์ดˆ๊ธฐ ๋ฌธ์„œ ์กฐ๊ฐ๋“ค

๋‚ด๊ฐ€ ๋งŒ๋“ค ๋ฌธ์„œ ์กฐ๊ฐ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

requestHeaders(),
requestFields(),
responseHeaders(),
responseFields()


์ผ๋‹จ ํ•ด๋ณด์ž.(์ด๊ฑฐ ๋ง๊ณ  ๋งŽ๋‹ค.)

requestHeaders๋Š”
์š”์ฒญ header๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
๊ทธ ๋‚ด์šฉ์€ ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด๋ฉด ๋˜๋Š”๋ฐ,
๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /api/delivery/
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/hal+json", Content-Length:"137"]
             Body = {"item":"book","user":"klom","deliveryTime":"2021-02-24T22:39:40.333417","deliveryEndTime":"2021-03-06T22:39:40.333437","itemPrice":null}
Session Attrs = {}

(์ด๊ฑด ์ž‘์„ฑํ•œ ์‚ฌ๋žŒ๋งˆ๋‹ค ๋‹ค๋ฅผ ๊ฒ๋‹ˆ๋‹ค.)
์—ฌ๊ธฐ์„œ header์— ๋ฌด์—‡์ด ๋“ค์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
Content-Type, Accpet, Content-Length ํ•„๊ธฐ!

headerWithName(HttpHeaders.CONTENT_TYPE).description("ContentType is application"),
headerWithName(HttpHeaders.ACCEPT).description("Accept is application/hal+json"),
headerWithName(HttpHeaders.CONTENT_LENGTH).description("137")

๋‚˜๋Š” ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ–ˆ๋‹ค. ๋‚ด์šฉ์€ ์ž์œ ๋กญ๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.
์š”์ฒญ ๋ฌธ์„œ ์กฐ๊ฐ์€ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ–ˆ๋‹ค.

requestHeaders(
       headerWithName(HttpHeaders.CONTENT_TYPE).description("ContentType is application"),
       headerWithName(HttpHeaders.ACCEPT).description("Accept is application/hal+json"),
       headerWithName(HttpHeaders.CONTENT_LENGTH).description("137")),
requestFields(fieldWithPath("item").description("what is your item"),
       fieldWithPath("user").description("how is made"),
       fieldWithPath("deliveryTime").description("start time"),
       fieldWithPath("deliveryEndTime").description("end time"),
       fieldWithPath("itemPrice").description("item price is"))

์ด์ œ ์‘๋‹ต๋„ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•˜์ž.

MockHttpServletResponse:
           Status = 201
    Error message = null
          Headers = [Location:"http://localhost:8080/api/delivery/1", Content-Type:"application/hal+json"]
     Content type = application/hal+json
             Body = {"id":1,"item":"book","user":"klom","deliveryTime":"2021-02-24T22:39:40.333417","deliveryEndTime":"2021-03-06T22:39:40.333437","status":"READY","itemPrice":null,"deliveryCost":5000,"_links":{"query-events":{"href":"http://localhost:8080/api/delivery"},"update-events":{"href":"http://localhost:8080/api/delivery/1"},"self":{"href":"http://localhost:8080/api/delivery/1"}}}
    Forwarded URL = null
   Redirected URL = http://localhost:8080/api/delivery/1
          Cookies = []

์š”์ฒญ๊ณผ ๋˜‘๊ฐ™์ด ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

responseHeaders(headerWithName(HttpHeaders.LOCATION).description("Location!"),
              headerWithName(HttpHeaders.CONTENT_TYPE).description("Content-Type is application/hal+json")),
responseFields(fieldWithPath("id").description("this item id"),
               fieldWithPath("item").description("what is your item"),
               fieldWithPath("user").description("what your name"),
               fieldWithPath("deliveryTime").description("start time"),
               fieldWithPath("deliveryEndTime").description("end time"),
               fieldWithPath("status").description("current status"),
               fieldWithPath("itemPrice").description("item price is "),
               fieldWithPath("deliveryCost").description("delivery cost")))

์ด๋ ‡๊ฒŒ ์‹คํ–‰ํ•˜๋ฉด ๋ ๊นŒ?

๋Œ๋ ค๋ณด์ž.

์–ด๋””์„œ ์ž˜๋ชป๋œ๊ฒƒ์ผ๊นŒ?
๋‚˜๋Š” ๋ถ„๋ช… ๋‹ค ์ž‘์„ฑํ–ˆ... ์–ด๋ผ..

์ƒ๊ฐํ•ด๋ณด๋‹ˆ ์‘๋‹ต ์ฝ”๋“œ์—๋Š” links๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์žŠ๊ณ  ์žˆ์—ˆ๋‹ค.
์ด๊ฒƒ์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ 2๊ฐ€์ง€๋ฅผ ์†Œ๊ฐœํ•˜๊ฒ ๋‹ค.

relaxedResponseFields์œผ๋กœ ์ˆ˜์ •ํ•œ๋‹ค.

์ด ๋ฐฉ๋ฒ•์„ ํ•˜๊ฒŒ๋˜๋ฉด ๋‚ด๊ฐ€ ์ž…๋ ฅํ•œ ๋ถ€๋ถ„๋งŒ ๋ฌธ์„œ๋กœ ๋งŒ๋“ค์–ด ์ค€๋‹ค.
ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ์‹ค์ˆ˜๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋นผ๋จน๊ณ  ์‹คํ–‰ํ•œ๋‹ค๋ฉด, 
๊ทธ๋ž˜๋„ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค. ์œ„ ์‚ฌ์ง„์—์„œ ํ™•์ธํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์—†๋Š” ์‘๋‹ตํ˜น์€ ์š”์ฒญ์—๋Š” ์กด์žฌํ•˜์ง€๋งŒ ๋ฌธ์„œ๋กœ ๋งŒ๋“ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ์œ„ ์ฒ˜๋Ÿผ ์•Œ๋ ค์ค€๋‹ค.

์žฅ์ : ๋‚ด๊ฐ€ ์ž…๋ ฅํ•œ ๊ฒƒ๋งŒ ๋ฌธ์„œ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.
๋‹จ์ : ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•ด์ฃผ์ง€ ์•Š๋Š”๋‹ค.

๋‹ค์Œ์„ ์ž‘์„ฑํ•œ๋‹ค.

fieldWithPath("_links.*.*").description("link information")))

์ด๋ ‡๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด ๋ฌธ์„œ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์›ฌ์ง€ ๊ฐ™์€ ๊ณ„์ธต์œผ๋กœ ๊ตฌ์„ฑ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ˜น์‹œ... ๋˜๋‚˜ ํ•˜๋Š” ๋งˆ์Œ์œผ๋กœ ํ–ˆ๋Š”๋ฐ ๋‹คํ–‰ํžˆ ๋งŒ๋“ค์–ด์กŒ๋‹ค.
์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์ง„ ๋ฌธ์„œ ์กฐ๊ฐ๋“ค์€

์ด๋Ÿฐ์‹์œผ๋กœ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋Œ“๊ธ€

Designed by JB FACTORY