μ€λλ§μ κΈ°μ νμ μμ±νκ² μ΅λλ€. μ¬μ€ JWT κ΄λ ¨ν΄μ κΈμ κ³μ μμ±νκ³ μμλλ°, νλ‘μ νΈλ μκ³ κ°κ°μ ν μ§ μΌλ§ λμ§ μμ λ°μ κ²λ μμ΄μ ν κΈμ κ±°μ ν λ¬ λ§μ μ¬λ¦¬λ€μ.. π νλ‘μ νΈλ₯Ό νλ©° λ°μκ² λ κ²λ μμ§λ§ κ·Έ λλΆμ REST Docsμ λν΄ λ μ λ€λ£° μ μκ² λμ΄ μ΄ κ³Όμ μ 곡μ ν©λλ€.
μ΄μ κΈμμλ document λ©μλλ‘ κ°λ¨ν docs ν¨ν€μ§ μλμ html λ¬Έμλ€μ΄ 보κ΄λλ κ²κΉμ§ λ€λ£¨μλλ°μ, μ΄λ²μλ ν μ€νΈλ₯Ό ν΅ν΄ μ€μ λ¬Έμκ° μμ±λλλ‘ νλ κ²μ μμλ³΄κ² μ΅λλ€.
κΈ°μ‘΄ ν μ€νΈ μ½λ
κΈ°μ‘΄ ν μ€νΈ μ½λλ μλμ κ°μ΅λλ€.
// import ννμ μλ΅
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
@WebMvcTest(BoardController.class)
public class BoardControllerWebMvcTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private BoardService boardService;
@Test
void κ²μκΈμ_μ μ₯νλ€() throws Exception {
// given
BoardWriteRequest request = new BoardWriteRequest("test title", "test content");
Board newBoard = κ²μκΈ_id_μμ();
when(boardService.write(request)).thenReturn(newBoard);
// when & then
mockMvc.perform(post("/boards")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(jsonPath("$.id").value(newBoard.getId()))
.andExpect(jsonPath("$.title").value(newBoard.getTitle()))
.andExpect(jsonPath("$.content").value(newBoard.getContent()))
.andDo(print())
.andDo(document("boards/save"));
}
@Test
void κ²μκΈμ_μ‘°ννλ€() throws Exception {
// given
Board writeBoard = κ²μκΈ_id_μμ();
when(boardService.findBoardById(any())).thenReturn(writeBoard);
// when & then
mockMvc.perform(get("/boards/" + writeBoard.getId()))
.andExpect(jsonPath("$.title").value(writeBoard.getTitle()))
.andExpect(jsonPath("$.content").value(writeBoard.getContent()))
.andDo(print())
.andDo(document("boards/find"));
}
}
μ΄μ λ°λ₯Έ REST Docs κ²°κ³Όλ μλμ²λΌ λμ΅λλ€. (μμ²μ κ²½μ°)
curl-request.adoc
curl-requestλ CURL μμ² νμμ 보μ¬μ€λλ€.
http-request.adoc
http-request.adocμ μ€μ HTTPμμ μ΄λ€ μμΌλ‘ μμ²μ΄ νλ¬κ°λμ§ (HTTP λ©μλ, κ²½λ‘ λ±)λ₯Ό λͺ¨λ λ΄κ³ μμ΅λλ€.
request-body.adoc
request-body.adocμ HTTP μ€ body λΆλΆλ§μ 보μ¬μ€λλ€.
κΈ°μ‘΄ λ¬Έμμ μμ¬μ΄ μ
κΈ°μ‘΄ λ¬Έμλ μ νν title, content λ±μ΄ μ΄λ€ λ»μΈμ§λ₯Ό λͺ μνμ§ μμ΅λλ€. λν, λ§μ½ ν€λμ μΈμ¦ μ 보 (Authorization)κ° λ€μ΄κ°λ€λ©΄ μ΄λ¬ν λΆκ°μ μΈ μ 보λ λ΄μ μ μμ΅λλ€. (μ΄λ μλ΅μμλ λμΌν©λλ€.)
λ°λΌμ, restdocs μμ μλ λ©μλλ€λ‘ μ€λͺ μ μ§μ μμ±ν μ μμ΅λλ€.
Request, Response Fields
곡μ λ¬Έμλ₯Ό μ°Έκ³ νμ¬ Requestμ Responseμ νλλ₯Ό μμ±ν΄ λ³΄κ² μ΅λλ€.
μμ²κ³Ό μλ΅μ νλλ org.springframework.restdocs.payload.PayloadDocumentationμ requsetFields, responseFieldsλ₯Ό νμ©νμ¬ λ΄μ©μ μΆκ°ν μ μμ΅λλ€.
package org.springframework.restdocs.payload;
import ...
public abstract class PayloadDocumentation {
private PayloadDocumentation() {
}
...
public static RequestFieldsSnippet requestFields(FieldDescriptor... descriptors) {
return requestFields(Arrays.asList(descriptors));
}
public static RequestFieldsSnippet requestFields(List<FieldDescriptor> descriptors) {
return new RequestFieldsSnippet(descriptors);
}
...
public static ResponseFieldsSnippet responseFields(FieldDescriptor... descriptors) {
return responseFields(Arrays.asList(descriptors));
}
public static ResponseFieldsSnippet responseFields(List<FieldDescriptor> descriptors) {
return new ResponseFieldsSnippet(descriptors);
}
...
}
μμ²κ³Ό μλ΅μ ꡬ쑰λ₯Ό 보λ, FieldDescriptorκ° κ³΅ν΅μΌλ‘ μ°μ΄λ κ² λ³΄μ λλ€. λ΄λΆ μ½λλ₯Ό λ³΄κ² μ΅λλ€.
- pathλ JSON λ΄μμμ κ²½λ‘λ₯Ό λ»ν©λλ€.
- typeμ λ¬μ¬λ νλμ νμ (String, Number λ±)μ λ»ν©λλ€.
- isOptionalμ ν΄λΉ νλκ° νμμ μΈμ§, μ΅μ μΈμ§λ₯Ό λ»ν©λλ€.
κ·Έλ°λ° μμ ν μ€νΈ μ½λλ₯Ό 보면, description μ¦ "μ€λͺ "μ΄ μμ±λμ΄ μλ κ²μ λ³Ό μ μμ΅λλ€. κ·Έλ°λ° μ§κΈ FieldDescriptorμμλ 보μ΄μ§ μμ΅λλ€. μ΄κ²μ FieldDescriptor > IgnorableDescriptor<FieldDescriptor> > AbstractDescriptor<T extends AbstractDescriptor<T>>μ μμ΅λλ€.
attributesλ νλ ν μ΄λΈμ λ€μ΄κ° μλ κ°μ 컀μ€ν νκ² λ°κΏ λ μ μ©νκ² μ¬μ©ν μ μμ΅λλ€. μλλ κ·Έ μμμ λλ€.
@Test
void κ²μκΈμ_μ μ₯νλ€() throws Exception {
// given
BoardWriteRequest request = new BoardWriteRequest("test title", "test content");
Board newBoard = κ²μκΈ_id_μμ();
when(boardService.write(request)).thenReturn(newBoard);
// when & then
mockMvc.perform(post("/boards")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(jsonPath("$.id").value(newBoard.getId()))
.andExpect(jsonPath("$.title").value(newBoard.getTitle()))
.andExpect(jsonPath("$.content").value(newBoard.getContent()))
.andDo(print())
.andDo(document("boards/save",
requestFields(
fieldWithPath("title").description("μ λͺ©"),
fieldWithPath("content").description("λ΄μ©")
.attributes(
key("path").value("hello")
)
)
));
}
ν΄λΉ ν μ€νΈ μ½λλ₯Ό μ€ννλ©΄ μλ‘μ΄ μ€λν«μΈ request-fields.adocμ΄ μμ±λ©λλ€.
κ²°λ‘ λ° νμ₯ μ
μ΄λ² κΈμ κ°λ¨νκ² λ§λ¬΄λ¦¬νλ € ν©λλ€. μ¬λ €λ 곡μ λ¬Έμλ₯Ό μ°Έκ³ νλ©΄, μμ μ΄ μνλ λλ‘ μ¬μ‘°μ ν μ μμ΅λλ€.
κ·Έλ¦¬κ³ μ΄λ κ² μ€λν« νμΌλ€μ΄ μμ±λλ©΄, μνλ μμλ‘ adoc νμΌλ€μ λ°°μΉνλ©΄ λ©λλ€.
μλ μμλ νλ‘μ νΈλ₯Ό νλ©΄μ μμ±ν ν μ€νΈ μ½λμΈλ°μ, μμ λ§ν attributesλ₯Ό μ΄μ©νμ§ μλλ€λ©΄ Pathμ κ²½μ° λ°°μ΄μ μμμλ hobbies[].hobby μ΄λ° μμΌλ‘ κΉλνμ§ μκ² ννλλ μ (μ΄λ° μμΌλ‘ μμ±νμ§ μμ κ²½μ° ν μ€νΈ μ μμΈκ° λ°μν©λλ€.)μ ν΄κ²°ν μ μμ΅λλ€.
μ΄λ₯Ό htmlλ‘ νννλ©΄ μλμ κ°μ΄ λμ΅λλ€.
λ€μ κΈμ μμ±νλ€λ©΄ νμ¬ κ²μν JSONμ΄ ν μ€λ‘ ννλλ λ¬Έμ κ° μλλ°, μ΄λ₯Ό λ§μ§λ§ μ¬μ§μ²λΌ μ λ ¬μμΌ ννν μ μλ λ°©λ²μ λν΄ μμ보λλ‘ νκ² μ΅λλ€.
λ§μ§λ§ μ£Όμμ¬ν!
λ§μ§λ§μΌλ‘ μλ €λ릴 κ²μ, μ‘°νμ κ²½μ° νΉμ νλλ₯Ό μμ²μ λ΄μ§ μλ κ²½μ°κ° λ§μ΅λλ€. κ·Έλ κΈ°μ κ°λ μμ² adoc νμΌμ μμ λΉμλ²λ¦΄ λκ° μμ κ²μ λλ€.
κ·Έλ¬λ μ΄λ κ² λλ©΄ μ΄λ URLλ‘ μμ²μ νλμ§ μ ν λͺ¨λ₯΄κΈ° λλ¬Έμ, μμ² νλκ° μλλΌλ http-request.adoc νμΌμ κΌ λ£μ΄μ£Όμλ κ² μ’μ΅λλ€.