ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring REST Docs 세팅기🚀
    카테고리 없음 2022. 8. 7. 18:22

     

    0. intro

    저희는 기존엔 API 명세를 notion을 통해 작성했습니다.

    하지만 실제 코드와 연동되지 않는 점이 불편했고, API별 테스트를 통한 검증을 하고 싶었습니다.

    이에 신뢰성있는 API 문서를 프론트엔드에 공유할 수 있는 REST Docs를 사용하기로 했습니다.

    다음은 REST Docs 를 세팅하고 문서화 한 과정입니다.

    1. build.gradle

    plugins {
       id 'org.springframework.boot' version '2.7.1'
       id 'io.spring.dependency-management' version '1.0.11.RELEASE'
       id 'java'
       id "org.asciidoctor.jvm.convert" version "3.3.2"
    }
    
    group = 'com.wooteco'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    
    configurations {
       asciidoctorExtensions // dependencies 에서 적용한 것 추가
    }
    
    repositories {
       mavenCentral()
    }
    ext {
       // 사용할 변수 선언
       set('snippetsDir', file("build/generated-snippets"))
    }
    
    dependencies {
       implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
       implementation 'org.springframework.boot:spring-boot-starter-validation'
       implementation 'org.springframework.boot:spring-boot-starter-web'
       implementation 'org.springframework.boot:spring-boot-starter-mail'
       implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.3'
       implementation 'com.google.guava:guava:31.0.1-jre'
    
       // build/generated-snippets 에 생긴 .adoc 조각들을 프로젝트 내의 .adoc 파일에서 읽어들일 수 있도록 연동
       // .adoc 파일을 HTML로 만들어 export
       asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
       compileOnly 'org.projectlombok:lombok'
    
       runtimeOnly 'com.h2database:h2'
       runtimeOnly 'mysql:mysql-connector-java'
    
       annotationProcessor 'org.projectlombok:lombok'
    
       testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    
       testImplementation 'org.springframework.boot:spring-boot-starter-test'
       testImplementation 'io.rest-assured:rest-assured:4.4.0'
       testImplementation 'io.rest-assured:spring-mock-mvc:4.4.0'
       testImplementation 'org.mockito:mockito-inline:3.8.0'
    
       implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
       runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
       runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
    }
    
    test {
       // 위에서 작성한 snippetsDir 디렉토리를 test의 output으로 구성하는 설정 
       // -> 스니펫 조각들이 build/generated-snippets로 출력
       outputs.dir snippetsDir
    
       // 테스트 실행
       useJUnitPlatform()
    }
    
    // asciidoctor 작업 구성
    asciidoctor {
       // 위에서 작성한 configuration 적용
       configurations 'asciidoctorExtensions'
       
       // source가 없으면 .adoc파일을 전부 html로 만들어버림
       // source 지정시 특정 adoc만 HTML로 만든다.
       sources{
          include("**/index.adoc","**/common/*.adoc")
       }
    
       // 특정 .adoc에 다른 adoc 파일을 가져와서(include) 사용하고 싶을 경우 경로를 baseDir로 맞춰주는 설정
       // (개별 adoc으로 운영한다면 필요 없는 옵션)
       baseDirFollowsSourceFile()
    
       // snippetsDir 를 입력으로 구성
       inputs.dir snippetsDir
    
       // asciidoctor 전 test 실행!
       dependsOn test
    }
    
    // static/docs 폴더 비우기
    asciidoctor.doFirst {
       delete file('src/main/resources/static/docs')
    }
    
    // asccidoctor 작업 이후 생성된 HTML 파일을 static/docs 로 copy
    task createDocument(type: Copy) {
       dependsOn asciidoctor
    
       from file("build/docs/asciidoc")
       into file("src/main/resources/static")
    }
    
    // build 전 createDocument(REST Docs 문서화) 실행!
    build {
       dependsOn createDocument
    }

    Gradle 7.4.1 버전

    grovvy 문법을 정확히 알지 못해 틀린 부분이 있을 수 있습니다.

    1-1. 빌드 과정

    1. 테스트 실행
    2. 테스트 결과를 build/generated-snippets에 저장
    3. 이전 static/index.html 비우기
    4. src/docs/asciidoc/index.adoc을 통해 build/docs/asciidoc/index.html 생성
    5. 생성된 HTML 파일을 build 에서 src/main/resources/static/로 이동한다

    사실 이때 index.html 이 생성되는 주기를 언제로 할지 고민했습니다.

    build 단위로 재생성을 할 시 build 시간이 길어지기 때문에, test 마다 실행 시간이 길어진다는 문제가 있었습니다.

    asciidoctor 명령어를 이용해 수동 export 를 하는 방안도 고려했지만,
    아직 실행 시간이 길어진 정도가 크지 않아 현재 세팅을 유지하고 있습니다.

    2. 테스트 코드 작성

    2-1. RestAssuredMockMvc

    RestAssuredMockMvc

    Spring의 MockMvc 위에 빌드된 REST API인 RestAssuredMockMvc 를 사용했습니다.

     

    RestAssured 와 문법이 같고, @WebMvcTest 형태의 테스트가 가능합니다.

    편한 문법으로 빠르게 테스트하기 위해 해당 API를 사용했습니다.

    관련 링크

    공식문서

    RestAssuredMockMvc - spring-mock-mvc 2.8.0 javadoc

     

    RestAssuredMockMvc - spring-mock-mvc 2.8.0 javadoc

    Latest version of com.jayway.restassured:spring-mock-mvc https://javadoc.io/doc/com.jayway.restassured/spring-mock-mvc Current version 2.8.0 https://javadoc.io/doc/com.jayway.restassured/spring-mock-mvc/2.8.0 package-list path (used for javadoc generation

    www.javadoc.io

    baeldung

    https://www.baeldung.com/spring-mock-mvc-rest-assured

     

    REST-assured Support for Spring MockMvc | Baeldung

    Learn how to test Spring REST controllers using the RestAssuredMockMvc API from REST-assured.

    www.baeldung.com

    2-2. 테스트 환경 세팅 (ControllerTest)

    import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
    import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
    
    // ... 등록된 컨트롤러/ MockBean 임포트
    
    import io.restassured.module.mockmvc.RestAssuredMockMvc;
    import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.restdocs.RestDocumentationContextProvider;
    import org.springframework.restdocs.RestDocumentationExtension;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @WebMvcTest({
            PostController.class,
            // ... 테스트할 컨트롤러 등록
    })
    @ExtendWith(RestDocumentationExtension.class)
    public class ControllerTest {
    
        protected MockMvcRequestSpecification restDocs;
    
        @MockBean
        protected PostService postService;
        // ... MockBean 등록
    
        // RestDocs 세팅
        @BeforeEach
        void setRestDocs(WebApplicationContext webApplicationContext,
                         RestDocumentationContextProvider restDocumentation) {
    
            restDocs = RestAssuredMockMvc.given()
                    .mockMvc(MockMvcBuilders.webAppContextSetup(webApplicationContext)
                            .apply(documentationConfiguration(restDocumentation)
                                    .operationPreprocessors()
                                    .withRequestDefaults(prettyPrint())
                                    .withResponseDefaults(prettyPrint()))
                            .build())
                    .log().all();
        }
    }

    2-3. 테스트 작성 방식

    class HashtagControllerTest extends ControllerTest { // (0)
    
        @DisplayName("해시태그로 검색 시 없는 해시태그이면 404 반환")
        @Test
        void findPostsWithHashtags_Exception_NoHashtag() {
            // Mockito로 등록된 MockBean 세팅
            doThrow(new HashtagNotFoundException())
                    .when(hashtagService) // (1)
                    .findPostsWithHashtag(matches("없는태그"), any());
    
            // 테스트 수행
            restDocs
                    .contentType(MediaType.APPLICATION_JSON_VALUE)
                    .when().get("/posts?hashtag=없는태그&size=5&page=0")
                    .then().log().all()
                    .assertThat() // (2)
                    .apply(document("search/byHashtag/fail/noHashtag")) // (3)
                    .statusCode(HttpStatus.NOT_FOUND.value());
        }

     

    (0) 위의 ControllerTest 를 상속합니다.

    (1) hashtagServiceControllerTest@MockBean으로 등록되어 있습니다.

    (2) 가독성을 돕는 코드. 아래 내용들을 검증합니다.

    (3) build/docs/asciidoc/ 안의 해당 디렉토리에 결과 adoc 파일들이 저장됩니다.

     

    3. AsciiDocs 작성

    3-1. adoc 조각들 생성

    build/generated-snippets 속 adoc 조각들

    빌드하면

    build/generated-snippets 폴더에

    테스트에서 작성한 board/create/fail 디렉토리에 adoc 조각들이 생성되어 있습니다.

     

    기본적으로 다음과 같은 조각들이 default로 생성됩니다.

    curl-request.adoc
    http-request.adoc
    httpie-request.adoc
    http-response.adoc
    request body
    response body

    우리는 이 중 http-request.adoc (요청) ,

    http-response.adoc (응답)을 사용합니다.

    이외 파일들은 사용하지 않습니다.

     

    3-2. index.adoc 문서 작성

    1) 플러그인 설치

    AsciiDoc

    우선 adoc 파일 작성의 편의를 위해 AsciiDoc 플러그인을 설치합니다.

    해당 플러그인으로 adoc 결과물을 gui 형태로 볼 수 있고, 문법 확인도 더 간편해집니다!

    2) 문서 작성

    index.adoc

    이제 API 문서를 adoc 형태로 작성하면 됩니다.

    src/docs/asciidoc/ 디렉토리 안에 index.adoc 파일을 만들고 문서를 작성합니다.

    (노가다…)

     

    = 속닥속닥 API 명세
    :doctype: book
    :icons: font
    :source-highlighter: highlightjs // 코드들의 하이라이팅을 highlightjs를 사용
    :toc: left // Table Of Contents(목차)를 문서의 좌측에 두기
    :toclevels: 2 // 목차 레벨 설정
    :sectlinks:
    :sectnums: // 분류별 자동으로 숫자를 달아줌 
    :docinfo: shared-head
    
    == 메인페이지 // "="는 마크다운 "#"과 같은 기능
    
    === 메인페이지 조회
    ==== 성공
    operation::board/find/content[snippets='http-request,http-response'] // 특정 adoc 파일 자동 임포트
    
    //... 아래 부분들 직접 작성
    

     

    이때

    operation::디렉토리명[snippets=’원하는 조각들’]

    를 통해 문서로 사용할 조각들을 명시해 자동으로 가져올 수 있습니다.

     

    자세한 사용법 링크

    https://narusas.github.io/2018/03/21/Asciidoc-basic.html

     

    Asciidoc 기본 사용법

    Asciidoc의 기본 문법을 설명한다

    narusas.github.io

     

    3-3. 빌드 후 index.html 생성

    이제 빌드하면

    index.html

    위에 작성한 index.adoc 파일이 index.html 형태로 export됩니다!

    index.html 파일은

    build/docs/asciidoc/index.html
    src/main/resources/static/index.html

     

    두 카테고리 안에 있습니다.

    ( build.gradlecreateDocument 명령어로 복사했습니다.)

    이중 저희가 웹 상에 보여줄 문서는 src/main/resources/static/ 안에 위치해 있습니다.

    문서 링크

    1) 깃허브 push

    이제 src/main/resources/static/index.html 파일을 pr을 날려

    깃허브 레포에 병합합니다.

    2) README.md에 링크 추가

    ## 속닥속닥
    
    [RESTDocs 로 구현된 API 명세](backend/sokdak/src/main/resources/static/index.html)
    

    README.md에 링크를 남겨주시면

     

    깃허브 메인화면

    다음과 같이 index.html에 접근 가능합니다.

     

    3) Github Pages 설정

    Github Pages

    좀더 편하게 API 문서를 보기 위해 github pages를 이용합니다.

    이때

    • main 브랜치로 활성화하면 main 브랜치 속 index.html로
    • dev 브랜치로 활성화하면 dev 브랜치 속 index.html로

    접근 가능합니다.

    현재는 dev 브랜치로 설정해두었습니다.

     

    결과

    이제

    https://woowacourse-teams.github.io/2022-sokdak/

     

    속닥속닥

    말하기 힘들었던 이야기들을 익명의 힘을 빌려 자유롭게 꺼낼 수 있는 우리만의 대나무숲, 속닥속닥

    woowacourse-teams.github.io

    로 들어갈 시

    README.md 가 뜹니다.

     

    링크를 클릭 시

    API 명세

    언제 어디서나 해당 링크로 API 명세를 볼 수 있습니다.

    개발자 뿐 아니라 유저들도 해당 API 문서에 접근 가능합니다.

     

    created by 헌치

     

Designed by Tistory.