ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Rest Docs 적용하기
    프로젝트/WebRTC 화상통화 프로젝트 2022. 7. 21. 00:01
    반응형

    개요

    프런트엔드와 원활하게 소통하기 위해 API 명세서를 만드려고 합니다.

    이때 코드를 기반으로 API 명세서를 만들어주는 Swagger와 Spring RestDocs가 있습니다.

     

    이 둘에 대해 비교해보고 RestDocs를 선택하게 되었습니다.

    https://junuuu.tistory.com/318

     

    API 문서화를 위한 Swagger와 Spring Rest Docs 비교

    Swagger란? API 문서를 자동으로 만들어주는 라이브러리입니다. REST API를 편리하게 문서화해주고, 이를 통해 편리하게 API를 호출해보고 테스트할 수 있는 프로젝트입니다. 이를 활용하여 협업하는

    junuuu.tistory.com

     

    컨트롤러에 작성한 MockMvc 단위 테스트로 Spring Rest Docs를 만드는 법을 알아보겠습니다.

     

    Spring Rest Docs 최소 요구사항

    Java 8

    Spring Framework 5

     

    Spring Rest Docs 공식문서

    바로가기

     

    Spring Rest Docs 기초지식

    API에 대해 읽기 쉬운 문서를 자동으로 생성해주는 라이브러리입니다.

     

    동작 과정을 요약하면 아래와 같습니다

     

    테스트 코드를 돌리면 "스니펫"이라고 하는 문서 조각을 만들어줍니다.

     

    이 조각을 모아서 문서로 만들기 때문에 저희는 스니펫들로 .adoc 파일을 생성해줍니다.

     

    Asciidoctor를 이용하여 HTML을 생성합니다.

     

    html로 변환 된 파일을 static 폴더로 이동시킵니다.

     

    개발자가 해야하는 과정

    RestDocs 관련 build.gradle 세팅하기

     

    테스트 코드 작성하기

     

    스니펫으로 .adoc 파일 만들어주기

     

     

    1단계 build.gradle 세팅하기

    plugins { 
    	id "org.asciidoctor.jvm.convert" version "3.3.2"
    }
    //asciidoctor의 플러그인으로 .aodc 파일을 .html으로 바꾸어서 특정 위치로 파일 이동시키기 위한 플러그인입니다.
    
    configurations {
    	asciidoctorExt 
    }
    
    dependencies {
    	asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' 
    	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' 
    //mockmvc를 기반으로 작성하기 때문에 spring-restdocs-mockmvc에 대한 의존성을 추가합니다.
    }
    
    ext { 
    	snippetsDir = file('build/generated-snippets')
    }
    //테스트 후 스니펫들이 들어갈 경로
    
    test { 
    	outputs.dir snippetsDir
    }
    
    asciidoctor { 
    	inputs.dir snippetsDir 
    	configurations 'asciidoctorExt' 
    	dependsOn test 
    }
    
    bootJar {
    	dependsOn asciidoctor
    	copy {
    		from "${asciidoctor.outputDir}"            
    		into 'src/main/resources/static/docs'
    	}
    }
    // 생성된 문서를 static/docs로 복사하는 과정입니다.

     

    2단계 테스트 코드 작성하기

    @AutoConfigureRestDocs
    

    테스트할 컨트롤러에 Sring Rest Docs 자동 설정 어노테이션을 달아줍니다. 

     

    이후에 .andDo(document("스니펫 파일명")) 을 통해 스니펫들을 생성해줍니다.

    @AutoConfigureRestDocs
    @WebMvcTest(OAuthController.class)
    class OAuthControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @MockBean
        private OAuthService oAuthService;
    
        @Test
        void Oauth_로그인() throws Exception {
            //given
            DefaultOAuth2User user = makeDefaultOAuth2User();
            Authentication authentication = new UsernamePasswordAuthenticationToken(user, "password", user.getAuthorities());
    
            //when
            ResultActions resultActions = mockMvc.perform(get("/oauth/loginInfo").with(authentication(authentication)));
    
            //then
            resultActions.andExpect(status().isOk())
                    .andDo(document("login"));
        }
    
        private DefaultOAuth2User makeDefaultOAuth2User() {
            //{sub=107986715009708108628, provider=google, name=김준우, email=bababrll@naver.com}
            Map<String, Object> customAttribute = new LinkedHashMap<>();
            customAttribute.put("sub", "107986715009708108628");
            customAttribute.put("provider", "google");
            customAttribute.put("name", "김준우");
            customAttribute.put("email", "bababrll@naver.com");
    
            return new DefaultOAuth2User(
                    Collections.singleton(new SimpleGrantedAuthority("USER")),
                    customAttribute,
                    "sub");
        }
    
    }

    테스트를 실행하면 login이라는 이름의 스니펫들이 만들어질 것으로 예상됩니다.

     

     

    테스트 실행 후 build/generated-snippets에 adoc 파일들이 만들어집니다.

     

    3단계 스니펫으로 .adoc 파일 만들기

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

     

    setting -> plugins -> AsciiDoc 다운로드

     

    src/docs/asciidoc에 index.adoc파일을 생성합니다.

    src/docs/asciidoc/index.adoc

    그리고 index.adoc에 다음과 같이 작성합니다.

    = REST Docs 문서 만들기 (글의 제목)
    부제목(부제)
    :doctype: book
    :icons: font
    :source-highlighter: highlightjs // 문서에 표기되는 코드들의 하이라이팅을 highlightjs를 사용
    :toc: left // toc (Table Of Contents)를 문서의 좌측에 두기
    :toclevels: 2
    :sectlinks:
    
    [[Member-API]]
    == Member API
    
    [[Member-로그인]]
    === 로그인
    operation::login[snippets='http-request,path-parameters,http-response,response-fields']

     

    위에서 AsciiDoc 플러그인을 설치했다면 로컬에서 문서가 어떻게 구성되는지 확인할 수 있습니다.

     

    Intellij의 하단에 Terminal로 이동해서 ./gradlew build 명령어를 수행합니다.

     

    intellij의 하단 terminal으로 이동
    resources/static/docs/index.html 파일 생성

    정상적으로 파일이 생성되었습니다.

     

    이후 Application을 실행시키고 localhost:8080/docs/index.html으로 이동하면 다음과 같은 화면을 볼 수 있습니다.

     

    이 정도까지가 튜토리얼정도로 이해하시면 될 것 같습니다.


    기능 고도화

     

    1. 응답 헤더 문서화

     

    테스트하는 컨트롤러에서 4가지의 헤더를 반환하는 상황입니다.

     

    컨트롤러에서 반환하는 헤더 4가지를 API 문서화 시킵니다.

    .andDo(document("login",
    
                            responseHeaders(
                                    headerWithName("accessToken").description("인증토큰"),
                                    headerWithName("refreshToken").description("갱신토큰"),
                                    headerWithName("accessTokenExpiration").description("인증토큰 만료일"),
                                    headerWithName("refreshTokenExpiration").description("갱신토큰 만료일")
                            )
                    ));

     

    이후에 index.aodc으로 이동해서 response-headers를 추가합니다.

    operation::login[snippets='custom,http-response,response-headers,response-fields']

     

    결과는 다음과 같습니다.

    accessToken, refreshToken, accessTokenExpiration, refreshTokenExpiration의 헤더값의 정보가 HTTP 응답 메시지에 포함됩니다.

     

    2. 응답 필드 문서화

    응답되는 RESPONSE-BODY는 다음과 같습니다.

    {
    "message":"success",
    "data":{"name":"김준우","email":"bababrll@naver.com","provider":"naver","nickname":null}
    }

     

    컨트롤러가 반환하는 body를 문서화 시킵니다.

    responseFields(
            fieldWithPath("message").description("응답 메시지"),
            fieldWithPath("data.name").description("사용자 이름"),
            fieldWithPath("data.email").description("사용자 이메일"),
            fieldWithPath("data.provider").description("로그인 제공사"),
            fieldWithPath("data.nickname").description("사용자 닉네임")
    )

     

    결과는 다음과 같습니다

    3. 출력 예쁘게 하기

    HTTP 응답 메시지에서 출력되는 데이터는 다음과 같이 일자로 나옵니다.

    조금 더 보기 편하게 하기위해서 다음과 같은 코드를 입력하면 됩니다.

    preprocessResponse(prettyPrint())

    이제 출력이 보기좋게 나옵니다.

     

    4. 요청 필드 문서화

       .andDo(document("set-nickname",
                                 preprocessRequest(prettyPrint()),                             
                                 requestFields(
                                         fieldWithPath("memberId").description("회원 아이디"),
                                         fieldWithPath("nickname").description("설정할 닉네임")
                                 )
    ));

    5. 요청 PathVariable 문서화

            resultActions.andExpect(status().isOk())
                         .andDo(document("get-icons",
                                 pathParameters(
                                         parameterWithName("member-id").description("회원 아이디")
                                 )
                         ));

     

    반응형

    댓글

Designed by Tistory.