- Published on
Spring Rest Docs Test 코드 작성
[Spring] Spring Rest Docs Test 코드 작성
이전 시간에는
Gradle설정 방법에 대해 알아봤습니다.
이번 시간에는 어떻게Test 코드를 작성하면snippets을 생성할 수 있는지 알아보겠습니다.작성된 모든 코드는 Github 에서 확인 하실 수 있습니다.
문서화에 사용되는 메소드
타입과필수값은Custom을 통해 만들수 있습니다. (type은 기본적용인 곳도 있음)Custom하는 방법에 대해서는Asciidoc사용방법에서 자세하게 다루겠습니다.
typeAttr defaultAttr 두 메소드는 제가 만든 메소드 입니다. 아래 예시 코드에 있습니다.
Document
MockMvcRestDocumentation.document: 문서 작성은 해당 메소드 안에서 이루어줘야 합니다.- 첫 번째 인자로는
snippets이 생성될 폴더 경로를 지정해 줍니다. - 두 번째 인자부터 각
요청및응답에 대해 정의합니다. (아래 메소드 참조)
- 첫 번째 인자로는
result.andDo(document("accounts/find-all",...)
Request Headers
HeaderDocumentation.requestHeader:Request Headers를 정의합니다.HeaderDocumentation.headerWithName: 각Header에 대한 정의를 합니다.
requestHeaders(
headerWithName(HttpHeaders.CONTENT_TYPE).description("요청 Body 타입"),
headerWithName(HttpHeaders.ACCEPT).description("기대 응답 Body 타입")
)

Path Parameters
RequestDocumentation pathParameters':Path Parameters를 정의합니다.RequestDocumentation.parameterWithName: 각Path Parameter에 대해 정의합니다.

Request Parameters
RequestDocumentation.requestParameters:Request Parameters를 정의합니다.RequestDocumentation.parameterWithName: 각Parameter에 대해 정의합니다.
requestParameters(
parameterWithName("page").description("페이지 번호 (0부터 시작)").attributes(typeAttr(JsonFieldType.NUMBER)).optional(),
parameterWithName("size").description("개수").attributes(typeAttr(JsonFieldType.NUMBER)).attributes(defaultAttr(20)).optional(),
parameterWithName("sort").description("정렬 {fieldName,asc|desc}").attributes(typeAttr(JsonFieldType.STRING)).optional()
),

Request Fields
PayloadDocumentation.requestFields:Request Fields를 정의합니다.PayloadDocumentation.fieldWithPath: 각Field에 대해 정의합니다.
requestFields(
fieldWithPath("name").description("이름").type(JsonFieldType.STRING),
fieldWithPath("age").description("나이").type(JsonFieldType.NUMBER)
)

Response Headers
HeaderDocumentation.responseHeaders:Response Headers를 정의합니다.HeaderDocumentation.headerWithName: 각Response Header를 정의합니다.
responseHeaders(
headerWithName(HttpHeaders.CONTENT_TYPE).description("응답 Body 타입")
),

Response Fields
PayloadDocumentation.responseFields:Response Fields를 정의합니다.PayloadDocumentation.fieldWithPath: 각Field를 정의합니다.

Links
HATEOAS를 적용 중이라면, Links에 대해서도 작성할 수 있습니다.
HypermediaDocumentation.links:Links를 정의합니다.HypermediaDocumentation.linkWithRel: 각Link에 대해 정의합니다.
links(
linkWithRel("self").description("요청 API 링크"),
linkWithRel("profile").description("요청 API 문서 링크")
),

Links의 경우 이렇게 정의 하더라도,Response Fields부분에서 한 번 더 정의해야 합니다. (불편)
Test 작성 예시
Test코드를 작성해야 해당 Test 코드를 기반으로 snippet을 생성해줍니다.
Rest API는 작성되어 있다고 가정하고 하겠습니다.
Rest Docs Config
기본적으로 Json 응답 값이 한줄로 보이기 때문에, Json 객체로 보기 이쁘게 하는 설정을 해줍니다.
@TestConfiguration
public class SpringRestDocsConfig {
@Bean
public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
return (it) -> {
it.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint())
.withResponseDefaults(Preprocessors.prettyPrint());
};
}
}
@WebMvcTest(AccountApi.class)
@AutoConfigureRestDocs
@Import(SpringRestDocsConfig.class)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class AccountApiTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private AccountService accountService;
private Attributes.Attribute defaultAttr(int value) {
return new Attributes.Attribute("defaults", value);
}
private Attributes.Attribute typeAttr(JsonFieldType type) {
return new Attributes.Attribute("types", type);
}
@Test
void 계정을_정상적으로_전체조회하면_200상태를_받는다() throws Exception {
// given
var api = "/api/accounts";
int page = 1;
int size = 5;
var params = new LinkedMultiValueMap<String, String>();
params.add("page", Integer.toString(page));
params.add("size", Integer.toString(size));
params.add("sort", "id,asc");
var accountResponses = List.of(new AccountResponse(1L, "jojiapp", 26));
var apiResponse = ApiResponse.of(accountResponses);
var pageable = PageRequest.of(page, size, Sort.by("id").ascending());
given(accountService.findAll(pageable)).willReturn(accountResponses);
// when
var result = mockMvc.perform(get(api)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.params(params)
);
// then
result.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(apiResponse)))
.andDo(document("accounts/find-all",
requestHeaders(
headerWithName(HttpHeaders.CONTENT_TYPE).description("요청 Body 타입"),
headerWithName(HttpHeaders.ACCEPT).description("응답 Body 타입")
),
requestParameters(
parameterWithName("page").description("페이지 번호 (0부터 시작)").attributes(typeAttr(JsonFieldType.NUMBER)).optional(),
parameterWithName("size").description("개수").attributes(typeAttr(JsonFieldType.NUMBER)).attributes(defaultAttr(20)).optional(),
parameterWithName("sort").description("정렬 {fieldName,asc|desc}").attributes(typeAttr(JsonFieldType.STRING)).optional()
),
responseHeaders(
headerWithName(HttpHeaders.CONTENT_TYPE).description("응답 Body 타입")
),
responseFields(
fieldWithPath("body[0].id").description("계정 고유 아이디").type(JsonFieldType.NUMBER),
fieldWithPath("body[0].name").description("이름").type(JsonFieldType.STRING),
fieldWithPath("body[0].age").description("나이").type(JsonFieldType.NUMBER)
)
));
}
}
@WebMvcTest(AccountApi.class): 해당Controller만 간단하게 테스트 하기 위해서@WebMvcTest를 사용했습니다.@AutoConfigureRestDocs:Rest Docs와 관련된 설정을 자동으로 해주는 어노테이션입니다. 추가하지 않을 시, 문서작성이 되지 않으므로 꼭 추가합니다.@Import(SpringRestDocsConfig.class): 위에서 만든 설정파일을 추가합니다.@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class): 테스트 메소드명의 구분자_를 공백으로 치환시켜 주는 어노테이션입니다.MockMvc: 가짜 요청을 보내기 위한 객체 입니다.mockMvc.perform()메소드를 통해API요청을 보낼 수 있습니다.HTTP Method는MockMvcRequestBuilders클래스와RestDocumentationRequestBuilders로 나뉘는데, 테스트 결과 둘 다 사용 가능합니다. ( 저는RestDocumentationRequestBuilders를 사용했습니다.)
테스트를 실행 시켜보면
build/generated-snippets/accounts/find-all위치에snippets이 생기는것을 확인할 수 있습니다.다음 시간에는
snipptes을 이용하여Asciidoc를 작성하는 방법과 문서를Custom하는 방법에 대해 알아보겠습니다.