spring rest docs - open api docs 적용

ttomy·2023년 8월 8일
0

프로젝트를 진행하며 프론트와 백엔드의 API에 대한 싱크를 맞추기 위해 API 문서화의 필요를 느껴 rest docs를 적용했다. 어떤 과정으로 적용할 수 있었는지 기록하려 한다.

  1. Spring REST Docs, restdocs-api-spec 의존성 추가.
  2. 테스트 코드로 adoc snippets을 생성
  3. snippets을 통해 open api스펙에 맞는 yml파일을 만들기

위의 과정을 통해 API를 문서화 한다.

1. Spring REST Docs를 위한 의존성 추가.

spring 공식문서를 보면 아래와 같은 의존성 설정을 한다.

org.springframework.restdocs의 의존성을 추가하고,
Asciidoctor를 추가하고,snippets가 생성될 경로나 asciidoctor가 무엇을 기반으로 할지 등을 설정하고 있다.

asciidoctor은 홈페이지에 가면 Asciidoctor is a fast, open source, Ruby-based text processor for parsing AsciiDoc® into a document model and converting it to output formats such as HTML 5, DocBook 5, manual pages, PDF, EPUB 3, and other formats.
라는 소개를 하고 있다. 즉 Asciidoc을 html5,pdf등의 형식으로 변환해주는 도구이다.
이에 대해 더 알고 싶다면 Asciidoctor Gradle Plugin Suite문서를 읽어보기를 바란다.

snippets은 spring restDocs에 의해 생성되는 .adoc형식의 문서파일을 말한다.

우리 프로젝트에서는 mockmvc가 아닌 restAssured를 사용하므 dependencies부분은 예시와 다르게 수정하고,
adoc을 html5가 아닌 yml파일로 변환하기를 원하기에 asciidoctor의존성은 설정하지 않겠다. 대신 epages에서 spring rest docs와 연동하여 oas파일을 만들어주는 오픈소스 라이브러리를 이용한다.
https://github.com/ePages-de/restdocs-api-spec#gradle
해당 readMe를 참고해 rest-docs-api문서의 의존성도 추가해준다.
결과적으로 의존성 설정은 아래와 같다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'com.epages.restdocs-api-spec' version '0.18.2'
}

group = 'com.project'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

repositories {
    mavenCentral()
}

dependencies {
 	...

    //Rest Assured
    testImplementation 'io.rest-assured:rest-assured:5.3.1'

    //Rest Docs
    testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
    testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2'
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2'
}

ext {
    snippetsDir = file('build/generated-snippets')
}

openapi3 {
    server = 'http://localhost:8080'
    title = 'USER-API'
    description = '사용자 리소스 API 입니다.'
    version = '0.1.0'
    format = 'yaml'
}

test {
    useJUnitPlatform()
    outputs.dir snippetsDir
}

여기서 restAssured만 사용하면서 왜 mockmvc까지 추가했냐는 의문이 들 수 있다.

    testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2'
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2'

하지만 'com.epages:restdocs-api-spec-mockmvc' 의존성까지 추가해주지 않으면 아래 이미지처럼 테스트 코드에 사용할 document 메소드를 찾아오지를 못한다...🥲

라이브러리의 readMe에는 아래처럼
RestAssured를 사용할 경우 MockMvc대신 restAssured의존성을 추가하라고 되어있는데... 안내가 잘못된 건지 새로운 이슈인건지 모르겠지만 둘 다 추가해 주어야 document에 메서드를 사용할 수 있었다.(2023.8.6일 기준)

이후 spring rest docs 공식문서에서는 asciidoctor를 통해 생성된 html5파일을 jar파일에 포함하도록 하는 설정을 하고 있다.

하지만 우리 프로젝트에서는 asciidoctor를 사용하지도 않고, 생성할 yml파일을 jar파일이 아닌 서버의 별도의 경로에 위치시킬 것이므로 이 설정은 하지 않는다.

snippets생성하기(Generating Documentation Snippets)

테스트 코드 setup

이제 snippets를 만들어 보자.
우선 테스트 class에 RestDocumentationExtension을 적용해야 한다.
RestDocumentationExtension은 테스트 코드를 실행할 때 테스트의 API의 호출과 응답정보를 캡처해 문서화 하는 역할을 한다.

RestAssured를 사용하는 경우 아래처럼 테스트 코드를 setUp한다.

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ExtendWith(RestDocumentationExtension.class)
public class BaseControllerTest {

    @LocalServerPort
    private int port;
    protected RequestSpecification spec;

    @BeforeEach
    void setUp(RestDocumentationContextProvider restDocumentation) {
        RestAssured.port = port;
        this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation))
                .build();
    }
}
  • port설정 시 주의점!
    아래처럼 RequestSpecification을 할당한 후에 RestAssured.port를 나중에 할당해 버리면Connection refused 에러가 발생한다.
@BeforeEach
    void setUp(RestDocumentationContextProvider restDocumentation) {
        this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation))
                .build();
        RestAssured.port = port;
    }

이러한 현상이 발생하는 이유는 RequestSpecBuilders는 restAssured의 port를 그대로 받아 구성되기 때문이다.

restAssured.port를 정하기 전에 RequestSpecBuilder를 통해 RequestSpecification을 만들어버리면 port가 UNDEFINED 상태라 connection이 실패하는 듯하다.

테스트 코드 작성

이제 https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/#documenting-your-api
이 부분을 참고해 테스트 코드에 document()를 작성해 문서화할 내용을 작성한 후, 테스트 코드를 실행하면 snippets이 생성된다.
아래는 예시이다.

      @Test
      @DisplayName("id로 멤버 조회"
      void findById() {
          //given
          final Member member = memberRepository.save(new Member("12413", "이름", "image"));
          final MemberResponse expected = MemberResponse.from(member);
          
          //when
          final MemberResponse response = RestAssured.given(spec).log().all()
                  .filter(document("멤버 조회",
                          pathParameters(
                                  parameterWithName("memberId").description("멤버 ID
                                  ), responseFields(
                                  fieldWithPath("id").description("멤버 아이디"),
                                  fieldWithPath("name").description("멤버 이름"),
                                  fieldWithPath("imageUrl").description("멤버 사진")
                          )
                      ))
                  .when()
                  .get("/members/{memberId}", member.getId())
                  .then()
                  .extract().response().as(MemberResponse.class);

          //then
          assertThat(response).isEqualTo(expected);
      }

이를 실행하면 아래처럼 build/generated-snippets경로에 snippet이 생성된 걸 볼 수 있다.

snippets을 기반으로 yml파일 생성하기

https://github.com/ePages-de/restdocs-api-spec
에 안내되어 있듯

./gradlew openapi3

명령어를 통해 build.gradle에 설정해놓은
openapi3 task를 수행하면

open-api 스펙의 yml파일이 생성된 것을 확인할 수 있다.


관련 코드는 요즘카페 Github에서 볼 수 있습니다.

REFERENCE

https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/#getting-started
https://github.com/ePages-de/restdocs-api-spec#gradle
https://tech.kakaopay.com/post/openapi-documentation/
https://dezang.net/blog/2021/09/05/spring-rest-docs-oas-02
https://helloworld.kurly.com/blog/spring-rest-docs-guide/

3개의 댓글

comment-user-thumbnail
2023년 8월 8일

잘 읽었습니다. 좋은 정보 감사드립니다.

1개의 답글
comment-user-thumbnail
2024년 7월 29일

RestAssuredRestDocumentationWrapper.document를 못읽어와서 고생하고 있었는데 감사합니다!

답글 달기