본문 바로가기
Spring Boot

[Spring Boot] Spring Rest Doc 설정(gradle)

by 오이가지아빠 2021. 6. 3.

#1. 프로젝트 생성 및 의존성 추가

프로젝트를 처음으로 생성하는 경우에는 Spring Initializr 에서 Spring Rest Docs 와 관련된 의존성을 추가합니다.

 

아래와 같이 최소한 Spring WebSpring REST Docs 는 추가해야 합니다.

기존의 프로젝트에 Spring REST Docs를 추가하는 경우에도 Spring initializr 를 사용해서 Dependencies를 추가한 다음,

자동으로 생성된 의존성을 복사해 오는 방법이 괜찮아 보입니다.

initializr가 자동으로 생성해 준 build.gradle 파일은 아래와 같습니다.

plugins {
	id 'org.springframework.boot' version '2.5.0'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'org.asciidoctor.convert' version '1.5.8'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

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

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

test {
	outputs.dir snippetsDir
	useJUnitPlatform()
}

asciidoctor {
	inputs.dir snippetsDir
	dependsOn test
}

뒷 부분에 다시 얘기하겠지만.. 참고로 제 경우에는 이 설정으로 doc의 코드조각인 snippets 까지는 생성됐지만

자동으로 html문서까지 만들어주진 않았기 때문에, 추가적으로 설정한 부분이 있습니다.

 

#2. 테스트 코드 작성

테스트 코드는 Junit4를 사용해서 작성했기 때문에 gradle 설정을 조금 바꿔주겠습니다.

Junit vintage 엔진을 추가해줍시다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

	// JUnit4
	testImplementation('org.junit.vintage:junit-vintage-engine') {
		exclude group: 'org.hancrest', module: 'hamcrest-core'
	}
}

테스트 코드는 아래와 같이 작성했습니다.

package com.example.demo.controller;

import com.example.demo.entity.TodoMngAct;
import com.example.demo.entity.TodoMngActId;
import com.google.gson.GsonBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Description;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriHost = "apihost", uriPort = 8090)
public class ItrmControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @Description("ITRM 저장")
    public void saveTodoMngAct() throws Exception {
        Map<String, String> param = new HashMap<>();
        param.put("TODO_DT", "20210526");
        param.put("TODO_ID", "HTW_BSP_A0002.1");
        param.put("DESCRIPTION", "테스트");
        param.put("PROGRESS", "S");
        TodoMngAct todoMngAct = new TodoMngAct(param);
        todoMngAct.setUPDATE_DT(LocalDateTime.now());

        String jsonString = new GsonBuilder().setPrettyPrinting().create().toJson(param);

        mockMvc.perform(
                post("/api/saveTodoMngAct")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(jsonString)
                        .accept(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andDo(document("itrm/{method-name}",
                        preprocessRequest(prettyPrint()),
                        preprocessResponse(prettyPrint()),
                        requestFields(
                                fieldWithPath("TODO_ID").description("TODO ID"),
                                fieldWithPath("TODO_DT").description("ITRM수행 일자"),
                                fieldWithPath("DESCRIPTION").description("점검 결과 기록"),
                                fieldWithPath("PROGRESS").description("성공이면 S, 실패면 NULL")
                        ))
                );
    }
}

MockMvc를 사용하는 테스트케이스에서 기본적으로

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc

여기까지는 기본적인 작성 방법이고, 


@AutoConfigureRestDocs(uriHost = "apihost", uriPort = 8090)

위 어노테이션을 추가하게 되면 RestDocs에 대한 기초 세팅들을 알아서 해줍니다. 커스터마이징이 더 필요하다면 이 설정 대신 커스텀 설정을 사용해도 됩니다.

uriHost 와 uriPort 값은 부가적인 내용으로, 이를 따로 기술하지 않으면 기본값인 localhost:8080 으로 Doc이 작성되어 집니다.

 

MockMvc를 사용하여, api를 호출하고, 간단히 결과는 status = 200 인지만 체크하는 테스트케이스이며

 

document("itrm/{method-name}",

// gradle 파일에 정의했던 build/generated-snippets 경로의 /itrm/{메소드이름}/ 폴더에 snippets 파일이 생성됩니다.

// 위 메소드 기준으로는 /itrm/save-todo-mng-act/ 로 생성됩니다.
preprocessRequest(prettyPrint()),

preprocessResponse(prettyPrint()),

// prettyPrint 옵션으로 json requets/response 를 보기 편하게 만들어 줍니다.
requestFields(
    fieldWithPath("TODO_ID").description("TODO ID"),
    fieldWithPath("TODO_DT").description("ITRM수행 일자"),
    fieldWithPath("DESCRIPTION").description("점검 결과 기록"),
    fieldWithPath("PROGRESS").description("성공이면 S, 실패면 NULL")
))

// request 필드에 대한 설명을 작성합니다.

 

#3. 테스트 수행/Snippets 생성

작성된 테스트케이스를 수행하고, 성공적으로 테스트가 완료되었다면,

아래와 같이 snippets 파일이 생성됩니다.

curl-request.adoc / http-request.adoc / http-response.adoc 등등의 조각파일이 생성되고,

http-request.adoc 파일을 하나 열어보면

위 처럼 http Request정보에 대한 조각 문서 내용을 가지고 있습니다.

이제 이 조각 파일들을 이용해서 하나의 문서를 만들어봅시다.

 

gradle 프로젝트 기준으로(Maven프로젝트는 설정이 다릅니다.) src/docs/asciidoc/index.adoc 문서를 생성합니다.

이 adoc 파일을 추후 static html 파일로 변환되어 url호출시 보여줄 최종 문서가 됩니다.

= ITRM API Document
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
:sectlinks:

[[introduction]]
== 소개

ITRM 자동화 처리를 위한 API

[[common]]
== 공통 사항

API PORT는 8090


[[save-todo-mng-act]]
== SAVE

=== Request

CURL:
include::{snippets}/itrm/save-todo-mng-act/curl-request.adoc[]

Request Parameters:
include::{snippets}/itrm/save-todo-mng-act/request-body.adoc[]
include::{snippets}/itrm/save-todo-mng-act/request-fields.adoc[]

Request HTTP Example:
include::{snippets}/itrm/save-todo-mng-act/http-request.adoc[]

=== Response
Response:
include::{snippets}/itrm/save-todo-mng-act/http-response.adoc[]

adoc 파일은 Mustache 문법으로 작성되며, 간단히 살펴 보면

테스트 결과로 생성된 조각파일들을 include::{snippets}/itrm/save-todo-mng-act/curl-request.adoc[] 와 같은 식으로 문서내에 include 시켜서 사용한다는 것을 알 수 있습니다.

 

 

#4. REST Doc문서 생성

이제 REST Doc 문서를 생성하기 위한 조각파일과, html 문서 생성을 위한 구조작성까지 마쳤으니, 실제 문서를 생성해봅시다.

 

gradle task > asciidoctor를 실행하면, build/asciidoc/html5/index.html 파일이 생성됩니다.

이 파일이 실제 REST Doc이 되는 html문서이고, 이제 우리는 이를 static resource에 포함해서 배포하기만 하면 됩니다.

 

다시 build.gradle 파일을 열고 추가적으로 내용을 작성해 줍니다.

task copyDocument(type: Copy) {
	dependsOn asciidoctor

	from file("build/asciidoc/html5/")
	into file("src/main/resources/static/docs")
}

build {
	dependsOn copyDocument
}

build task 실행 시, build/asciidoc/html5/ 폴더 하위의 파일들을 src/main/resources/static/docs 로 옮겨주는 작업을 추가하고 gradle build를 수행하면, 정적 html 파일이 resources/static/docs 하위에 복사 되면서 url을 통해 바로 호출할 수 있게 됩니다.

 

#5. 작성된 문서 확인

생성된 최종 빌드파일을 java -jar 를 통해 수행한 후, http://host:port/docs/index.html 을 호출하면 생성했던 REST Docs를 확인 할 수 있습니다.

java -jar demo-0.0.1-SNAPSHOT.jar

 

 

반응형

댓글