[Section 3] Spring Rest Docs

Kim·2022년 11월 11일
0

Boot Camp

목록 보기
47/64
post-thumbnail

Spring Rest Docs

Spring Rest Docs는 REST API 문서를 자동으로 생성해 주는 Spring 하위 프로젝트이다.
Spring Rest Docs의 가장 큰 특징은 Controller의 슬라이스 테스트를 통해 테스트가 통과 되어야만 API 문서가 정상적으로 만들어진다는 것이다.

Spring Rest Docs의 API 문서 생성 흐름

1. 테스트 코드 작성
ᅠᅠa. 슬라이스 테스트 코드 작성
ᅠᅠController에 대한 슬라이스 테스트 코드를 먼저 작성한다.

ᅠᅠb. API 스펙 정보 코드 작성
ᅠᅠ슬라이스 테스트 코드 다음에 Controller에 정의되어 있는 API 스펙 정보(Request Body, Response Body, Query Parameter, ...)를 코드로 작성한다.

2. 테스트 테스크 실행
ᅠᅠa. 작성된 슬라이스 테스트 코드 실행
ᅠᅠ하나의 테스트 클래스를 실행시켜도 되나, 일반적으로 Gradle의 build task 중 하나인 test task를 실행시켜 API 문서 스니핏을 일괄 생성한다.

ᅠᅠb. 테스트 실행 결과가 passed이면 다음 작업을 진행하고, failed이면 테스트 케이스를 수정한다.

3. API 문서 스피닛(.adoc 파일) 생성
ᅠᅠ테스트 케이스의 실행 결과가 passed이면, 테스트 코드에 포함된 API 스펙 정보 코드를 기반으로 API 문서 스니핏이 .adoc 확장자 파일로 생성된다.

스니핏(snippet)

문서의 일부 조각을 의미한다.
스니핏은 테스트 케이스 하나 당 하나의 스니핏이 생성된다. 여러 개의 스니핏이 모여 하나의 API 문서를 생성할 수 있다.

4. API 문서 생성
ᅠᅠ생성된 API 문서 스니핏을 모아 하나의 API 문서로 생성한다.

5. API 문서를 HTML로 변환
ᅠᅠ생성된 API 문서를 HTML 파일로 변환한다.
ᅠᅠHTML로 변환된 API 문서는 HTML 파일 자체를 공유할 수도, URL을 통해 해당 HTML에 접속해 확인할 수도 있다.

Spring Rest Docs 설정

Spring Rest Docs가 API 문서 생성 작업을 정상적으로 수행하려면 기본적인 설정 작업이 필요하다.

1. build.gradle 설정

plugins {
	...
	id "org.asciidoctor.jvm.convert" version "3.3.2"    // (1)
	id 'java'
}

group = ...
version = ...
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

// (2)
ext {
	set('snippetsDir', file("build/generated-snippets"))
}

// (3)
configurations {
	asciidoctorExtensions
}

dependencies {
    // (4)
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
  
    // (5) 
	asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

	...
}

// (6)
tasks.named('test') {
	outputs.dir snippetsDir
	useJUnitPlatform()
}

// (7)
tasks.named('asciidoctor') {
	configurations "asciidoctorExtensions"
	inputs.dir snippetsDir
	dependsOn test
}

// (8)
task copyDocument(type: Copy) {
	dependsOn asciidoctor            // (8-1)
	from file("${asciidoctor.outputDir}")   // (8-2)
	into file("src/main/resources/static/docs")   // (8-3)
}

// (9)
build {
	dependsOn copyDocument  
}

// (10)
bootJar {
	dependsOn copyDocument    // (10-1)
	from ("${asciidoctor.outputDir}") {  // (10-2)
		into 'static/docs'     // (10-3)
	}
}
  1. .adoc 확장자를 가지는 AsciiDoc 문서를 생성해주는 Asciidoctor를 사용하기 위한 플러그인

  2. ext 변수의 set() 메서드를 이용해서 API 문서 스니핏이 생성될 경로 지정

  3. AsciiDoctor에서 사용되는 의존 그룹을 지정
    :asciidoctor task가 실행되면 내부적으로 3에서 지정한 ‘asciidoctorExtensions’라는 그룹을 지정함

  4. 'org.springframework.restdocs:spring-restdocs-mockmvc'를 추가함으로써 spring-restdocs-corespring-restdocs-mockmvc 의존 라이브러리가 추가됨

  1. spring-restdocs-asciidoctor 의존 라이브러리 추가

  2. :test task 실행 시, API 문서 생성 스니핏 디렉토리 경로 설정

  3. :asciidoctor task 실행 시, Asciidoctor 기능을 사용하기 위해 :asciidoctor task에 asciidoctorExtensions 설정

  4. :build task 실행 전에 실행되는 task
    :copyDocument task가 수행되면 index.html 파일이 src/main/resources/static/docs 에 copy됨
    copy된 index.html 파일은 API 문서를 파일 형태로 외부에 제공하기 위한 용도로 사용할 수 있음
    ᅠ8-1. :asciidoctor task가 실행된 후, task가 실행 되도록 의존성을 설정
    ᅠ8-2. build/docs/asciidoc/ 경로에 생성되는 index.html을 copy한 후,
    ᅠ8-3. src/main/resources/static/docs 경로로 index.html을 추가

  5. :build task가 실행되기 전에 :copyDocument task가 먼저 수행 되도록 함

  6. 애플리케이션 실행 파일이 생성하는 :bootJar task 설정
    ᅠ10-1. :bootJar task 실행 전, :copyDocument task가 실행 되도록 의존성 설정
    ᅠ10-2. Asciidoctor 실행으로 생성되는 index.html 파일을 jar 파일 안에 추가해줌
    ᅠ jar 파일에 index.html을 추가해 줌으로써 웹 브라우저에서 접속해 API 문서를 확인할 수 있음

8에서 copy되는 index.html은 외부에 제공하기 위한 용도이다.
10에서는 index.html을 애플리케이션 실행 파일인 jar 파일에 포함해서 웹 브라우저에서 API 문서를 확인하기 위한 용도이다.

2. API 문서 스니핏을 사용하기 위한 템플릿(혹은 source 파일) API 문서 생성

build.gradle 설정을 완료했다면, 마지막으로 할 일은 API 문서 스니핏이 생성 되었을 때 이 스니핏을 사용해서 최종 API 문서로 만들어 주는 템플릿 문서(index.adoc)를 생성하는 것이다.

  • Gradle 기반 프로젝트에서는 src/docs/asciidoc/ 경로에 디렉토리를 생성해야 한다.

  • src/docs/asciidoc/ 디렉토리 내에 비어있는 템플릿 문서(index.adoc)를 생성하면 된다.

이렇게 하면 Spring Rest Docs를 사용하기 위한 사전 준비는 끝이다.

🔑Key Summary

  • Spring Rest Docs의 API 문서 생성 흐름은 다음과 같다.
    슬라이스 테스트 코드 작성 ➡ API 스펙 정보 코드 작성
    ➡ test 태스크 실행
    ➡ API 문서 스니핏 생성 ➡ 스니핏을 포함한 API 문서 생성
    .adoc 파일의 API 문서를 HTML로 변환

  • Spring Rest Docs를 사용해서 API 문서를 생성하기 위해서는 .adoc 문서 스니핏을 생성해주는 Asciidoctor가 필요하다.

  • HTML 파일로 변환된 API 문서는 외부에 제공할 수도 있고, 웹브라우저에 접속해서 API 문서를 확인할 수도 있다.


Controller 테스트 케이스에 Spring RestDocs 적용

Spring Rest Docs를 이용해 API 문서를 생성하기 위한 테스트 케이스의 기본 구조는 아래와 같다.

@WebMvcTest(MemberController.class)   // (1)
@MockBean(JpaMetamodelMappingContext.class)   // (2)
@AutoConfigureRestDocs    // (3)
public class MemberControllerRestDocsTest {
    @Autowired
    private MockMvc mockMvc;  // (4)

    @MockBean
	  // (5) 테스트 대상 Controller 클래스가 의존하는 객체를 Mock Bean 객체로 주입 받기

    @Test
    public void postMemberTest() throws Exception {
        // given
        // (6) 테스트 데이터 

        // (7) Mock 객체를 이용한 Stubbing

        // when
        ResultActions actions =
                mockMvc.perform(
                     // (8) request 전송
                );

        // then
        actions
                .andExpect( // (9) response에 대한 기대 값 검증)
                .andDo(document(
                            // (10) API 문서 스펙 정보 추가
                 ));
    }
}
  1. @SpringBootTest 애너테이션을 사용하지 않고, @WebMvcTest 애너테이션을 사용했다.
    @WebMvcTest 애너테이션은 Controller를 테스트 하기 위한 전용 애너테이션이다. 애너테이션의 괄호 안에는 테스트 대상 Controller 클래스를 지정한다.

  2. JPA에서 사용하는 Bean들을 Mock 객체로 주입해준다.

  3. Spring Rest Docs에 대한 자동 구성을 위해 @AutoConfigureRestDocs를 추가한다.

  4. MockMvc 객체를 주입받는다.

  5. Controller 클래스가 의존하는 객체(주로 서비스 클래스, Mapper)의 의존성을 제거하기 위해 @MockBean 애너테이션을 사용해 Mock 객체를 주입받는다.

  6. HTTP request에 필요한 request body나 query parmeter, path variable 등의 데이터를 추가한다.

  7. 5에서 주입받은 Mock 객체가 동작하도록 Mockito에서 지원하는 given() 메서드 등으로 Strubbing 한다.

  8. MockMvc의 perform() 메서드로 request를 전송한다. 슬라이스 테스트에서 사용했던 방법과 동일하다.

  9. response를 검증한다. ResultActions의 .andExpect() 역시 슬라이스 테스트에서 사용했던 방법과 동일하게 검증하면 된다.

  10. 테스트 수행 이후, API 문서를 자동으로 생성하기 위한 해당 Controller 핸들러 메서드의 API 스펙 정보를 document(...)에 추가한다.
    document(...) 메서드는 API 문서를 생성하기 위해 Spring Rest Docs에서 지원하는 메서드다.
    andDo(...) 메서드는 .andExpect()처럼 검증 작업을 하는 것이 아닌, 일반적인 동작을 정의하고자 할 때 사용한다.

@SpringBootTest vs @WebMvcTest

  • @SpringBootTest 애너테이션은 @AutoConfigureMockMvc 과 함께 사용되어 Controller를 테스트 할 수 있다.
    프로젝트에서 사용하는 전체 Bean을 ApplicationContext에 등록하여 사용한다.
    테스트 환경을 구성하는 것은 편리하지만 실행 속도가 상대적으로 느리다.
  • @WebMvcTest 애너테이션은 Controller 테스트에 필요한 Bean만 ApplicationContext에 등록하기 때문에 실행 속도는 상대적으로 빠르다. 하지만 Controller에서 의존하고 있는 객체가 있다면, 해당 객체에 대해 Mock 객체를 사용해 의존성을 일일이 제거해줘야 한다.

    @SpringBootTest는 주로 DB까지 요청 프로세스가 이어지는 통합 테스트에 주로 사용하고, @WebMvcTest는 주로 Controller`를 위한 슬라이스 테스트에 사용한다.

API 문서 생성을 위한 API 스펙 정보 추가

@WebMvcTest(MemberController.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
public class MemberControllerRestDocsTest {

    ...

    @Test
    public void postMemberTest() throws Exception {
        // given
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com", "홍길동", "010-1234-5678");
        String content = gson.toJson(post);

        MemberDto.response responseDto = new MemberDto.response(
            ...
        );

        given(...);

        // when
        ResultActions actions = mockMvc.perform(
        	...
        );

        // then
        actions
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.data.email").value(post.getEmail()))
                .andExpect(jsonPath("$.data.name").value(post.getName()))
                .andExpect(jsonPath("$.data.phone").value(post.getPhone()))
                .andDo(document(					// (9) 
                        "post-member",				// (9-1)
                        getRequestPreProcessor(),	// (9-2)
                        getResponsePreProcessor(),	// (9-3)
                        requestFields(				// (9-4)
                                List.of(
                                        fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), // (9-5)
                                        fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
                                        fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호")
                                )
                        ),
                        responseFields(		// (9-6)
                                List.of(
                                        fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
                                        fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"), // (9-7)
                                        fieldWithPath("data.email").type(JsonFieldType.STRING).description("이메일"),
                                        ...
                                )
                        )
                ));
    }
}

9document(…) 메서드는 API 문서를 생성하기 위해서 알아야 할 내용이다.

9. document(…) 메서드는 API 스펙 정보를 전달받아 실질적인 문서화 작업을 수행한다.

9-1. document(…) 메서드의 첫 번째 피라미터 9-1은 API 문서 스피닛의 식별자 역할을 한다. post-member로 지정했으므로 문서 스니핏은 post-member 디렉토리 하위에 생성된다.

9-2, 9-3. 문서 스피닛을 생성하기 전, request와 response에 해당하는 문서 영역을 전처리하는 역할을 한다.

9-4. requestFields(…)는 문서로 표현될 request body를 의미한다. 파라미터로 전달되는 List<FieldDescriptor> 의 원소인
FieldDescriptor 객체가 request body에 포함된 데이터를 표현한다.

9-5. request body를 JSON 포맷으로 표현 했을 때, 하나의 프로퍼티를 의미하는 FieldDescriptor이다.
type(JsonFieldType.STRING)은 JSON 프로퍼티의 값이 문자열임을 의미한다.

9-6. responseFields(…)는 문서로 표현될 response body를 의미한다. 파라미터로 전달되는 List<FieldDescriptor> 의 원소인
FieldDescriptor 객체가 response body에 포함된 데이터를 표현한다.

  • JsonFieldType.OBJECT는 JSON 포맷으로 표현 된 프로퍼티의 값이 객체임을 의미한다.
  • JsonFieldType.NUMBER는 JSON 포맷으로 표현 된 프로퍼티의 값이 int나 long 같은 Number임을 의미한다.

9-7. fieldWithPath("data.memberId")data.memberId는 data 프로퍼티의 하위 프로퍼티를 의미한다.

테스트 케이스를 실행하고 결과가 passed이면 작성한 API 스펙 정보를 기반으로 문서 스피닛이 만들어진다.

🔑Key Summary

  • @SpringBootTest 는 데이터베이스까지 요청 프로세스가 이어지는 통합 테스트에 주로 사용된다.
    @WebMvcTest 는 Controller를 위한 슬라이스 테스트에 주로 사용한다.

  • document(…) 메서드는 API 스펙 정보를 전달 받아서 실질적인 문서화 작업을 수행하는 RestDocumentationResultHandler 클래스에서 가장 핵심 기능을 하는 메서드이다.

  • OperationRequestPreprocessorOperationResponsePreprocessor를 이용해 API 문서를 생성 전에 전처리를 수행할 수 있다.

  • requestFields(…)는 문서로 표현될 request body를 의미하며, 파라미터로 전달되는 List<FieldDescriptor>의 원소인 FieldDescriptor 객체가 request body에 포함되는 데이터를 표현한다.

  • responseFields(…)는 문서로 표현될 response body를 의미하며, 파라미터로 전달되는 List<FieldDescriptor>의 원소인 FieldDescriptor 객체가 response body에 포함된 데이터를 표현한다.


스니핏을 이용한 API 문서화

src/docs/asciidoc 디렉토리를 생성하고 비어 있는 index.adoc” 파일을 생성한다.
Gradle 프로젝트의 경우, 템플릿 문서가 위치하는 디폴트 경로는 src/docs/asciidoc이다.

템플릿 문서 내용 추가

= 커피 주문 애플리케이션    // (1)
:sectnums:
:toc: left                
:toclevels: 4
:toc-title: Table of Contents
:source-highlighter: prettify

Kim <Sample@gmail.com>   // (2)

v1.0.0, 2022.11.11    // (3)

// (4)
***
== MemberController
=== 회원 등록
.curl-request
include::{snippets}/post-member/curl-request.adoc[]     

.http-request
include::{snippets}/post-member/http-request.adoc[]

.request-fields
include::{snippets}/post-member/request-fields.adoc[]

.http-response
include::{snippets}/post-member/http-response.adoc[]

.response-fields
include::{snippets}/post-member/response-fields.adoc[]

...

Asciidoc 문법으로 작성된 템플릿 문서이다.

  1. API 문서의 제목
  2. API를 생성한 사람의 정보
    12 사이에 있는 항목은 API 문서의 목차와 관련된 내용
  3. API 문서의 생성 날짜
  4. 테스트 케이스 실행을 통해 생성한 API 문서 스니핏을 사용하는 부분
    템플릿 문서에서 스니핏을 사용하는 방법은 정해져 있음
    include::{snippets}/스니핏 문서가 위치한 디렉토리/스니핏 문서파일명.adoc[]

템플릿 문서를 HTML 파일로 변환

IntelliJ 우측 상단의 [Gradle] 탭을 클릭하고 Tasks ➡ build에서 bootJar 또는 build를 더블 클릭한다.

정상적으로 빌드가 종료되면 src/main/resources/static/docs 디렉토리에 index.adoc 파일을 이용해 변환된 index.html 파일이 생성된다.

인텔리제이에서 애플리케이션을 실행하고 URL을 웹 브라우저에 입력하거나, index.html 파일에서 우측 상단에 보이는 아이콘을 클릭해 접속할 수 있다.

웹 브라우저에 접속하면 이러한 API 문서를 확인할 수 있다.


참고 자료

📔 Spring Rest Docs 적용

📄 Spring REST Docs
📄 Documenting your API
📄 JSON Field Types

📄 Working with Asciidoctor

0개의 댓글