본문 바로가기

STUDY/Spring

Spring Boot | REST Docs 적용하기 ( + build failed 해결.. )

Spring REST Docs?

Asciidoctor를 사용해 RESTful 서비스에 대한 문서를 생성하도록 돕는다.

Swagger vs REST Docs

Swagger UI Spring REST Docs
실제 코드에 애노테이션을 추가하여 문서가 작성되는 방식
-> 가독성 측면에서 좋지 않을 수 있음
-> 하지만 적용하는 방법이 쉬움!
테스트 코드로 문서를 작성하는 방식 (테스트가 성공해야만 작성됨)
-> 테스트 코드를 반드시 작성해야 함..
-> 약간의 진입 장벽이 있음
API를 테스트 해볼 수 있음! API 테스트 불가능 진짜 "문서"

Swagger도 사용해봤지만, REST Docs가 더 좋다고 느꼈다.. 일단 문서 생긴게 더 마음에 들기도 하고...^^

REST Docs 적용하기!

우아한형제들 기술 블로그의 Spring Rest Docs 적용글을 참고하며 진행했습니다.

build.gradle

plugins항목에 org.asciidoctor.convert를 추가

plugins {
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'org.asciidoctor.convert' version '1.5.6'    // asciidoc파일을 변환해주고, Build폴더에 복사해주는 플러그인
}

repositories항목 위에 아래의 내용을 작성!

asciidoctor {
    dependsOn test    // gradle build 시 test -> asciidoctor 순으로 수행
}

bootJar {
    dependsOn asciidoctor    // gradle build 시 asciidoctor 순으로 수행
    from("${asciidoctor.outputDir}/html5") {    // gradle build 시 ./build/asciidoc/html5/ 에 html 파일생성
        into 'static/docs'                        // html 파일이 jar 안의 /static/docs/ 폴더로 복사됨
    }
}

dependencies항목에 spring-restdocs-mockmvc를 추가

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

테스트 작성 (JUnit5 기준)

보통은 @WebMvcTest로 컨트롤러 레이어만 테스트하는 환경에서 REST Docs 작성을 하게된다.

  • JUnit5의 경우 @AutoConfigureRestDocs설정이 안 먹힌다..
  • @ExtendWithRestDocumentataionExtension.class를 설정
  • @WebMvcTest에는 테스트할(문서를 만들고자 하는) 컨트롤러를 설정

RestDocumentationExtention이 자동적으로 빌드 툴에 맞는 output 디렉터리를 설정한다.
Gradle의 경우 build/generated-snippets경로에 스니펫들이 생성됨

@ExtendWith(RestDocumentationExtension.class)    // JUnit5 필수
@WebMvcTest(ImageController.class)
public class ImageControllerRestDocsTest {

}

그리고 @BeforeEach메서드로 MockMvc설정을 해준다.

@Autowired
private MockMvc mockMvc;

@BeforeEach
void setup(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            .apply(springSecurity())    // springSecurity설정이 되어있지 않으면 생략
            .apply(documentationConfiguration(restDocumentation)).build();
}

테스트 메서드 작성하기

  • 테스트가 성공해야 스니펫이 생성된다.
  • @MockBean으로 주입된 서비스에 대해 적절한 처리를 해준다.. -> given().willReturn()
  • 요청, 응답에 대한 문서에 적힐 설명들을 작성
    • requestParameters는 queryString으로 요청하는 값에 대해..
    • responseFields는 응답 값에 대해.. 형식에 맞게 적어주는 것이 중요 -> 공식문서 참고
    • optional()을 붙여주면 필수 값이 아니라는 뜻 붙이지 않으면 테스트 실패하니 주의
@Test
@WithMockUser(...) // springSecurity 설정 없으면 생략
void getImages() throws Exception {

    List<HashMap<String, Object>> response = new ArrayList<>();
    HashMap<String, Object> data = new HashMap<>();
    data.put("name", "foo");
    data.put("img_url", "https://~~~");
    data.put("reg_date", "2021-06-03 10:00:00");
    response.add(data);

    given(imageService.findAllById(any(ParamDto.class))).willReturn(response);

    ResultActions perform = this.mockMvc.perform(
            RestDocumentationRequestBuilders.get("/images")
    );

    perform.andExpect(status().isOk())
            .andDo(print())
            .andDo(document("images-get",    // 설정한 값으로 스니펫 폴더가 생성됨
                requestParameters(
                    parameterWithName("user").description("유저 이름").optional()
                ),
                responseFields(
                    fieldWithPath("imgList").description("이미지 리스트"),
                    fieldWithPath("imgList[].name").description("이미지 등록 회원 이름"),
                    fieldWithPath("imgList[].img_url").description("이미지 url"),
                    fieldWithPath("imgList[].reg_date").description("이미지 등록일시")
                })
            );

}

그리고 테스트에 성공하면 build/generated-snippets경로에 .adoc파일들이 생성된다.

생성된 스니펫들을 사용하자!

.adoc파일을 생성해야 하는데, Gradle의 경우 src/docs/asciidoc/*.adoc경로로 생성해야 한다.
파일명은 자유롭게 설정하면 된다. .adoc으로 생성하기만 하면 OK

그리고 스니펫들을 사용하는.. 진짜 문서로서의 기능을 하도록 조합하면 끝!

  • =, ==... 들은 마크다운에서의 #, ##과 비슷하게 사용.. 제목이 된다
  • :toc: left설정은 왼쪽에 사이드바가 생성되고, 목차들에 링크가 자동으로 걸린다!!
  • :toclevels: 2설정은 목차들의 레벨을 어디까지 보여줄 것이냐..
= REST API
:toc: left
:toclevels: 2
:source-highlighter: highlightjs

== 1. 개요

== 2. 인증

== 3. 이미지

이제 생성된 스니펫들을 진짜로 포함해보자!
includ::{snippets}/../~.adoc[]으로 사용할 스니펫을 선택해서 불러오면 된다!
인텔리제이를 사용한다면 플러그인을 다운받아 작성하시길... 문법이 바로바로 이해가 쏙쏙...

== 3. 이미지
=== 3-1. 이미지 조회
===== Request
include::{snippets}/images-get/request-parameters.adoc[]
===== Request Example
include::{snippets}/images-get/http-request.adoc[]

===== Response
include::{snippets}/images-get/response-fields.adoc[]

===== Response Example
include::{snippets}/images-get/response-body.adoc[]

그럼 이렇게 자동으로 문서가 생성된다... 👏 (아직 미리보기 중..)


Gradle 버전에 맞게 수정하기!

현재 Gradle 7.0.2 버전을 사용중인데, 공식문서나 위에 설정한대로 실행하면 제대로 html파일이 생성되지 않는다...
아래와 같이 build.gradle을 수정한다.

 

build를 해보면 알 수 있겠지만 compileJava > test > asciidoctor > bootJar 순으로 실행된다. 

만약에 테스트도 통과하고 순서대로 다 실행되고 빌드 성공까지 떴는데 html파일이 생성되지 않으면.. 아래처럼 수정해보자...

plugins {
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'        // jvm.convert를 사용해야 한다!
}

group = '...'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'


// 여기 추가!
configurations {
    asciidoctorExtensions        
}

repositories {
    mavenCentral()
}

// 이 부분도 추가
ext {
    snippetsDir = file('build/generated-snippets')
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // 이 부분이 추가됨!
    asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'    
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

asciidoctor {
    configurations 'asciidoctorExtensions'
    inputs.dir snippetsDir
    dependsOn test
}

bootJar {
    dependsOn asciidoctor
    copy {
        from "${asciidoctor.outputDir}"            // /html5를 사용하지 않는다!
        into 'src/main/resources/static/docs'    // /static/docs로 복사!
    }
}

 

그리고 나서 build해주면

./gradlew build

 

이렇게 /build/docs/asciidoc/index.html이 생성된 것을 확인할 수 있다.

index.htmlbootJar가 실행되면서 /statics/docs로 복사된다.

이렇게 복사된 것 까지 확인했다면 성공!
jar를 실행한 뒤에 http://localhost:8080/docs/index.html로 접속하면 REST Docs가 잘 뜬다!!!!!

(중간에 api-doc.adoc을 index.adoc으로 변경했음. 본인이 지정한 파일명으로 접속하면 됨)

+ 참고

 

spring-projects/spring-restdocs

Test-driven documentation for RESTful services. Contribute to spring-projects/spring-restdocs development by creating an account on GitHub.

github.com