> Hello World !!!

     

@syaku

Spring RestDocs 작성 가이드 #1 기본편

Github: https://github.com/syakuis/spring-restdocs

Spring RestDocs 는 RestController 의 REST API 에 대해 테스트 코드를 작성한 것을 토대로 자동으로 API 문서를 생성할 수 있는 라이브러리이다.

개발 사양

  • Spring boot 2.4.5
  • Spring RestDocs 2.0.5
  • Asciidoctor

설정

Gradle 기반으로 작성되었습니다.

build.gradle

buildscript {
    ext.restDocsVersion = "2.0.5.RELEASE"

    dependencies {
        classpath "org.asciidoctor:asciidoctor-gradle-plugin:1.5.3"
    }
}

apply plugin: "org.asciidoctor.convert"

dependencies {
    asciidoctor "org.springframework.restdocs:spring-restdocs-asciidoctor:${restDocsVersion}"
    testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:${restDocsVersion}"
}

def snippetsDir = file("${buildDir}/generated-snippets")

asciidoctor {
    attributes "snippets": snippetsDir
    inputs.dir snippetsDir
    dependsOn test
}

asciidoctor.doFirst {
    delete file('src/main/resources/static/docs')
}

task copyAsciidoc(type: Copy) {
    dependsOn asciidoctor
    from file("${buildDir}/asciidoc/html5")
    into file("src/main/resources/static/docs")
}

test {
    outputs.dir snippetsDir
}

build {
    dependsOn copyAsciidoc
}

bootJar {
    dependsOn copyAsciidoc
    from ("${asciidoctor.outputDir}/html5") {
        into "BOOT-INF/classes/static/docs"
    }
}

순차적으로 작업에 대해 설명하겠습니다.

  • buildscript > asciidoctor gradle plugin 을 추가한다.
  • apply plugin > asciidoctor gradle plugin 을 활성화 한다.
  • dependencies > spring restdocs 라이브러리를 추가한다.
  • asciidoctor 작업에 대해 정의한다.
    • test 작업이 실행될때 asciidoctor 작업도 실행한다.
  • asciidoctor.doFirst > asciidoctor 작업이 실행되기 전에 실행한다.
  • copyAsciidoc 작업을 직접 만들었다.
  • build 작업이 실행될때 asciidoctor 작업도 실행한다.
  • bootJar 작업이 실행될때 asciidoctor 작업도 실행한다.
    • restdocs 문서 파일을 jar 배포 파일에 포함될 수 있도록 한다.

Gradle 작업에 대한 설명

  • asciidoctor: 테스트 코드를 기반으로 Asciidoctor 파일을 생성합니다.
  • copyAsciidoc: 생성된 Asciidoctor 파일을 웹 서비스에서 제공할 수 있도록 복사합니다.

템플릿이 되는 Asciidoc 용 index.aboc 생성하기

src/docs/asciidoc/index.adoc 파일을 생성하고 아래와 같이 합니다. (예시 입니다.)

ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:
:site-url: /build/asciidoc/html5/

= 제목

****
WARNING: 경고....
****

[[introduction]]
== 소개

[[introduction]]
== 서비스 환경

|===
| 환경 | URI

| 개발
| <http://127.0.0.1:5000>

| 운영
| <http://localhost:5000>
|===

ifndef::snippet[]
:snippet: ../../../build/generated-snippets
endif::[]

필요한 내용은 직접 작성하면 됩니다.

구현

테스트 코드만 가이드하였고 서비스 로직을 가진 구현체는 생략하였습니다.

Configuration

Spring RestDocs 공통 설정을 구현한다. test 패키지에 생성합니다.

@Configuration
public class RestDocsTestConfiguration {

    @Bean
    public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
        return configurer -> configurer.operationPreprocessors()
            .withRequestDefaults(prettyPrint())
            .withResponseDefaults(prettyPrint());
    }
}

Unit Test

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
class SignupRestControllerTest {
    @Autowired
    private MockMvc mvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void signup() throws Exception {

        AccountRequestDto.Signup request = AccountRequestDto.Signup.builder()
            .name("read")
            .username("man")
            .password("VlHv4")
            .build();

        mvc.perform(post("/accounts/v1/users/signup")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isCreated())

            .andDo(document("accounts/v1/users/signup",
                requestHeaders(
                    headerWithName(HttpHeaders.CONTENT_TYPE).description(MediaType.APPLICATION_JSON),
                    headerWithName(HttpHeaders.ACCEPT).description(MediaType.APPLICATION_JSON)
                ),

                requestFields(
                    fieldWithPath("username").description("사용자 이름"),
                    fieldWithPath("password").description("암호"),
                    fieldWithPath("name").description("이름")
                ),

                responseFields(
                    fieldWithPath("id").description("번호"),
                    fieldWithPath("username").description("사용자 이름"),
                    fieldWithPath("name").description("이름"),
                    fieldWithPath("disabled").description("비활성"),
                    fieldWithPath("blocked").description("잠금"),
                    fieldWithPath("uid").description("사용자 아이디"),
                    fieldWithPath("updatedOn").description("수정일"),
                    fieldWithPath("registeredOn").description("생성일")
                )
            ));
    }
}

문서 생성 및 추가 작업

  • Gradle asciidoctor 작업을 실행합니다.
  • build/generated-snippets/accounts/v1/signup 폴더에 adoc 파일이 생성됩니다.
    • 하이라이트된 경로는 테스트 코드에서 설정한 경로입니다. 아래 코드에 해당합니다.
      • .andDo(document("accounts/v1/users/signup",
  • requestHeaders() : 요청 헤더를 설정합니다.
  • requestFields(): 요청 Body 필드를 설정합니다.
  • responseFields(): 응답 Body 필드를 설정합니다.
  • 추가적으로
    • pathParameters(): 요청 url 에 대한 경로를 설정합니다.
    • requestParameters(): 요청 파라메터를 설정합니다.

pathParameters 를 사용할 경우

참고: https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-path-parameters

MockHttpServletRequestBuilder 대신 RestDocumentationRequestBuilders 를 사용해야 합니다.

Response Body 가 배열인 경우

참고: https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-request-response-payloads-fields-json

FieldDescriptor[] accountFields = Arrays.asList(
                    fieldWithPath("id").description("번호"),
                    fieldWithPath("username").description("사용자 이름"),
                    fieldWithPath("name").description("이름"),
                    fieldWithPath("disabled").description("비활성"),
                    fieldWithPath("blocked").description("잠금"),
                    fieldWithPath("uid").description("사용자 아이디"),
                    fieldWithPath("updatedOn").description("수정일"),
                    fieldWithPath("registeredOn").description("생성일")
);

// 아래와 같이 사용해야 합니다.
responseFields(fieldWithPath("[]").description("계정 목록"))
                    .andWithPrefix("[].", accountFields)

템플릿이 되는 Asciidoc 용 index.aboc 에 생성된 adoc 파일 추가하기

맨하단에 추가하면 됩니다.


== 사용자

=== 회원 가입

**Request**
include::{snippet}/accounts/v1/users/signup/http-request.adoc[]

**Request Header**
include::{snippet}/accounts/v1/users/signup/request-headers.adoc[]

**Request Body**
include::{snippet}/accounts/v1/users/signup/request-fields.adoc[]

**Response**
include::{snippet}/accounts/v1/users/signup/http-response.adoc[]

**Response Body**
include::{snippet}/accounts/v1/users/signup/response-fields.adoc[]

다음 Spring RestDocs 가이드에는 중복되는 코드들을 제거하고 문서 관리 효율적일 수 있는 방법을 제공하겠습니다.

참고링크