가자공부하러!

Spring REST Docs(5) - 문서조각 커스텀 본문

공부/Spring Boot

Spring REST Docs(5) - 문서조각 커스텀

오피스엑소더스 2020. 12. 28. 17:40

request-fields에 required 또는 optional 컬럼을 추가하고싶다면?

 

문서조각 템플릿을 커스텀 할 수 있다.

 

아래 경로에 커스텀하고싶은 템플릿 생성

경로 : src/test/resources/org/springframework/restdocs/templates

주의(!) 평소 패키지 추가하던것 처럼 ~/org.spring~.~.templates 이런식으로 온점으로 구분하면 안된다 경로기 때문에

 

테스트코드

여기서는 fieldWithPath 뒤에 optional()을 붙여줬다

package com.exam.restdocs.market;

import com.exam.restdocs.common.RestDocsConfiguration;
import com.exam.restdocs.domain.Employee;
import com.exam.restdocs.domain.Item;
import com.exam.restdocs.domain.Market;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.io.File;
import java.util.LinkedHashSet;

import static org.springframework.restdocs.headers.HeaderDocumentation.*;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.*;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
@Import(RestDocsConfiguration.class)
public class MarketTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

//  @Ignore
  @Test
  public void createMarket() throws Exception{
    String jsonData = FileUtils.readFileToString(new File("src/test/resources/market_1.json"));
    mockMvc
        .perform(
            post("/market/create")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonData))
              //.content(objectMapper.writeValueAsString(market))//objectMapper가 json string으로 바꿔줌
        .andDo(print())
        .andDo(document("create-market",
              links(
                  halLinks(),
                  linkWithRel("self").description("link to self")
              ),
              requestHeaders(
                  headerWithName(HttpHeaders.ACCEPT).description("accept header"),
                  headerWithName(HttpHeaders.CONTENT_TYPE).description("header content type")
              ),
              relaxedRequestFields(
                  fieldWithPath("marketName").description("Name"),
                  fieldWithPath("location").description("desc")
              ),
              responseHeaders(
                  headerWithName(HttpHeaders.LOCATION).description("resp header location")
              )
//            ,
//              responseFields(
//                  fieldWithPath("respName").description("resp name"),
//                  subsectionWithPath("respSub").description("sub section")
//              )
            )
        );
  }

  @Test
  public void createMarketEntity() throws Exception{
    String body = FileUtils.readFileToString(new File("src/test/resources/market_1.json"));

    mockMvc.perform(post("/market/create/entity")
        .contentType(MediaType.APPLICATION_JSON)//요청 타입은 JSON이다
        .accept(MediaTypes.HAL_JSON)//HAL JSON을 돌려달라
        .content(body)
    )
        .andDo(print())
        .andExpect(status().isCreated())
        .andExpect(jsonPath("market.id").exists())//id가 있는지 확인
        .andExpect(jsonPath("_links.self").exists())
        .andExpect(jsonPath("_links.markets").exists())
        .andExpect(jsonPath("_links.update-market").exists())
        //적용한 스니펫 : links
        .andDo(document("create-market",
              links(//링크정보
                  linkWithRel("self").description("link to self"),
                  linkWithRel("markets").description("link to show all markets"),
                  linkWithRel("update-market").description("link to update a market"),
                  linkWithRel("profile").description("link to update a market")
              ),
              requestHeaders(//요청 헤더
                  headerWithName(HttpHeaders.ACCEPT).description("accept type"),
                  headerWithName(HttpHeaders.CONTENT_TYPE).description("content type")
              ),
              requestFields(//요청 필드
                  fieldWithPath("marketName").description("name of new market").optional(),
                  fieldWithPath("location").description("location of market"),
                  fieldWithPath("employees.[].name").description("name of employee of market"),
                  fieldWithPath("employees.[].age").description("name of employee of market"),
                  fieldWithPath("items.[].name").description("name of item of market"),
                  fieldWithPath("items.[].category").description("category of item of market"),
                  fieldWithPath("items.[].quantity").description("quantity of item of market")
              ),
              responseHeaders(
                  headerWithName(HttpHeaders.LOCATION).description("accept type"),
                  headerWithName(HttpHeaders.CONTENT_TYPE).description("HAL JSON type")
              ),
              //responseFields(
              relaxedResponseFields(//relaxed : 문서의 일부분만 확인해도 되게끔 설정해주는 prefix
                  //fieldWithPath : 응답의 필드를 기술하기 위한 메소드
                  //subsectioniiWithPath : 하위섹션에 대한 정보를 기술하기 위한 메소드
                  subsectionWithPath("market").description("info of market"),
                  fieldWithPath("market.id").description("id of market"),
                  fieldWithPath("market.marketName").description("name of new market"),
                  fieldWithPath("market.location").description("location of market"),
                  subsectionWithPath("market.employees[]").description("employees of market"),
                  fieldWithPath("market.employees[].id").description("id of employee of market"),
                  fieldWithPath("market.employees.[].name").description("name of employee of market"),
                  fieldWithPath("market.employees.[].age").description("name of employee of market"),
                  fieldWithPath("market.employees.[].market").description("market of employee of market"),
                  subsectionWithPath("market.items").description("items of market"),
                  fieldWithPath("market.items.[].id").description("id of item of market"),
                  fieldWithPath("market.items.[].name").description("name of item of market"),
                  fieldWithPath("market.items.[].category").description("category of item of market"),
                  fieldWithPath("market.items.[].quantity").description("quantity of item of market"),
                  fieldWithPath("market.items.[].market").description("market of item of market"),
                  fieldWithPath("_links.profile").description("profile")
              )
            )
        )
    ;
  }
}

 

github.com/HyeongJunMin/SpringBootOnmacOS/blob/master/restdocs/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet

//request-fileds.snippet 
===== Request Fields
|===
|필드명|타입|필수값|설명

{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}}  //(2)
|{{#tableCellContent}}{{description}}{{/tableCellContent}}

{{/fields}}

|===

 

다른방법(spring restdocs 공식문서)

위 예제 처럼 optional() 메서드를 이용하지 않고 attributes() 메서드 활용

.andDo(document("create-user", 
  requestFields(		
    fieldWithPath("name").description("The user's name")
	  .attributes(key("constraints").value("Must not be null. Must not be empty")), 
	fieldWithPath("email").description("The user's email address")
	  .attributes(key("constraints").value("Must be a valid email address"))))); 
//request-fileds.snippet 
.{{title}} 
|===
|Path|Type|Description|Constraints 

{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{constraints}} 

{{/fields}}
|===

 

참고 : woowabros.github.io/experience/2018/12/28/spring-rest-docs.html

참고 : docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/#documenting-your-api-customizing-including-extra-information

Comments