아카이브

[스프링 기반 REST API 개발] Spring REST Docs 소개, 자동 설정 및 커스터마이징 본문

Spring/스프링 기반 REST API 개발

[스프링 기반 REST API 개발] Spring REST Docs 소개, 자동 설정 및 커스터마이징

주멘이 2021. 1. 6. 22:00

Spring REST Docs란? API 문서 작성 자동화 도구 

성공 Test Case를 기반으로 API 스펙을 작성하기 때문에, 제품 코드를 건드릴 필요가 없다

-  문서의 조각을 만드는 것을 snippets라고 칭한다.

- API 코드를 변경했을 때, Test Case가 성공하면 문서도 자동으로 바뀐다.

- 추가 테스트에 대한 문서화가 강제된다.

Asciidoctor ? HTML 문서 작성 도구

- Rest Docs가 문서 작성에 필요한 코드 조각을 만드는 도구라면, Asciidoctor는 Adoc 파일을 활용하여 html 문서를 만들어주는 도구이다.

테스트를 통한 snippets 생성 방법

- MockMvc(@WebMvcTest) : Contoller Layer 만 테스트하기에 속도가 빠르다.

- Rest Assured(@SpringBootTest) : 통합테스트로 context의 모든 bean을 주입받기 때문에 속도가 느리다.

REST Docs 자동 설정과 커스터마이징

1. REST Docs 자동 설정 @AutoConfigureRestDocs

- TestClass에 @AutoConfigureRestDocs 를 설정하면, target/generated-snippets dir를 생성하고 snippets를 추가해준다(overriding)

2. RestDocMockMvc 커스터마이징 

- @TestConfiguration을 커스터마이징 빈과 테스트 클래스에 기입

- RestDocsMockMvcConfigurationCustomizer 구현한 빈 등록

// test package 영역에
// RestDocMockMvc 커스터마이징 
@TestConfiguration
public class RestDocsConfiguration {

    @Bean
    public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
        return configurer -> configurer.operationPreprocessors()
                .withRequestDefaults(prettyPrint())	// 요청 본문 문서화
                .withResponseDefaults(prettyPrint())	// 응답 본문 문서화
                ;
    }
}

- TestClass에 @Import(RestDocsConfiguration.class) 를 통해 커스터마이징 빈을 가져온다

@AutoConfigureRestDocs
@Import(RestDocsConfiguration.class)    // bean import
public class EventControllerTest {
}

 

- andDo(document(“doc-name”, snippets))

- snippets에 해당하는 것

  • links​()
  • requestParameters() + parameterWithName()
  • pathParameters() + parametersWithName()
  • requestParts() + partWithname()
  • requestPartBody()
  • requestPartFields()
  • requestHeaders() + headerWithName()
  • requestFields​() + fieldWithPath()
  • responseHeaders() + headerWithName()
  • responseFields​() + fieldWithPath()
  • ...
  • Relaxed*
  • Processor
    • preprocessRequest(prettyPrint())
    • preprocessResponse(prettyPrint())
  •  

@Test 코드 일부

    @Test
    @TestDescription("정상적으로 이벤트를 생성하는 테스트")
    void createEvent() throws Exception {
        EventDto eventDto = EventDto.builder()
                .name("Spring")
                .description("REST API dev with Spring")
                .beginEnrollmentDateTime(LocalDateTime.of(2020, 12, 25, 12, 12))
                .closeEnrollmentDateTime(LocalDateTime.of(2020, 12, 26, 12, 12))
                .beginEventDateTime(LocalDateTime.of(2020, 12, 27, 12, 12))
                .endEventDateTime(LocalDateTime.of(2020, 12, 28, 12, 12))
                .basePrice(100)
                .maxPrice(200)
                .limitOfEnrollment(1000)
                .location("D2 Factory")
                .build();

        mockMvc.perform(post("/api/events/")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaTypes.HAL_JSON)
                .content(objectMapper.writeValueAsString(eventDto))
        )
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("id").exists())
                .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE))
                .andExpect(jsonPath("free").value(false))
                .andExpect(jsonPath("offline").value(true))
                .andExpect(jsonPath("eventStatus").value(Matchers.not(EventStatus.DRAFT)))

                .andExpect(jsonPath("_links.self").exists())
                .andExpect(jsonPath("_links.query-events").exists())
                .andExpect(jsonPath("_links.update-events").exists())
                .andDo(document("create-event"));
        ;
    }