Spring REST Docs 적용해보기

MinSeong Kang·2022년 7월 5일
1

spring

목록 보기
1/18

최근에 velog에서 tistory로 넘어가고, Spring Rest Docs에 대해서 정리하였다. 좀 더 깔끔하게 정리하였다고 생각하여 다시 공유해드립니다.
https://minnseong.tistory.com/25/

들어가기 전

프로젝트 백엔드 개발에서 프론트엔트 개발자와 REST API로 데이터를 주고 받기 위해 API 서버를 구축하고, API 문서를 하나하나 타이핑하여 wiki로 작성한 경험이 있다. 직접 타이핑 하면서 불편했던 점을 생각해보면, 아래 2가지 경우가 있었다.
1) 코드 수정시, API 문서도 잘 수정해주어야 하지만 까먹고 안한 경우가 많아 클라이언트 개발과 엇갈린 적이 많았다.
2) 내가 타이핑한 API 문서와 실제 코드를 실행했을 때 반환하는 API 스펙이 다른 경우가 꽤나 있었다.

API 문서를 직접 타이핑하여 wiki로 작성하지 않고, API 문서를 자동화하는 "Spring REST Docs"에 대해서 알게되었다. Spring Rest Docs는 실제 Production Code에 영향을 주지 않으며, Test Code 작성을 강제화하여 테스트 성공시에만 문서가 생성된다. 또한 버전 변화에 유연하고 정확성이 높다. 따라서 Spring Rest Docs는 위 불편한 경우들의 해결책이다. 이번 포스트에서는 프로젝트에 적용하는 방법과 사용하는 방법을 기록해보자 한다.

Build Configuration

  • java 17.0.1 , spring boot 2.7.1 , Gradle 7.4.1
  • 기존 프로젝트 gradle 파일에 아래와 같이 gradle을 추가하여 세팅해주었다.
  • gradle build 시 test -> asciidoctor -> bootjar 순으로 실행되며, build/docs/asciidoc에 html 문서가 생성되며, 이를 src/main/resources/static/docs 에 복사하여 저장한다.
  • gradle 설정은 각자 다 다르기 때문에 공식문서 를 참고하면 좋을 것 같다.
plugins {
	...
	id "org.asciidoctor.jvm.convert" version "3.3.2"
}

configurations {
	...
	asciidoctorExt
}

ext {
	asciidocVersion = "2.0.6.RELEASE"
	snippetsDir = file('build/generated-snippets')
}

dependencies {
	...
	asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:${asciidocVersion}"
	testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:${asciidocVersion}"
    ...
}

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"
	}
}

Test Code 작성

  • Spring Rest Docs를 사용하기 위해서는 무조건 Test Code를 작성해야 한다.
  • @ExtendWith(RestDocumentationExtension.class 는 필수 !
  • 공식문서를 보면 MockMvc를 @Before method 통해 설정해주었지만, 해당 프로젝트에서 uriHost 명을 설정하기 위해 @AutoConfigureRestDocs 어노테이션을 사용하였기 때문에 따로 @Before 과정이 필요 없었다.
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriScheme = "https", uriHost = "api.minlog.com", uriPort = 443)
@ExtendWith(RestDocumentationExtension.class)
public class PostControllerDocTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private PostRepository postRepository;

//    @BeforeEach
//    public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
//        this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
//                .apply(documentationConfiguration(restDocumentation))
//                .build();
//    }

    @Test
    @DisplayName("글 단건 조회")
    public void test1() throws Exception {

        Post post = Post.builder()
                .title("제목")
                .content("본문")
                .build();

        postRepository.save(post);

        this.mockMvc.perform(RestDocumentationRequestBuilders.get("/posts/{postId}", 1L)
                        .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andDo(document("post-inquiry",
                		RequestDocumentation.pathParameters(
                            RequestDocumentation.parameterWithName("postId").description("게시글 ID")
                        ),
                        PayloadDocumentation.responseFields(
                                PayloadDocumentation.fieldWithPath("id").description("게시글 ID"),
                                PayloadDocumentation.fieldWithPath("title").description("게시글 제목"),
                                PayloadDocumentation.fieldWithPath("content").description("게시글 내용")
                        )
                ));
    }


    @Test
    @DisplayName("글 등록")
    public void test2() throws Exception {

        PostCreate request = PostCreate.builder()
                .title("제목입니다.")
                .content("내용입니다.")
                .build();

        String requestJson = objectMapper.writeValueAsString(request);

        this.mockMvc.perform(RestDocumentationRequestBuilders.post("/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .content(requestJson))
                .andExpect(status().isOk())
                .andDo(document("post-create",
                        PayloadDocumentation.requestFields(
                                PayloadDocumentation.fieldWithPath("title").description("게시글 제목")
                                        .attributes((key("constraint").value("좋은 제목입력해주세요."))),
                                PayloadDocumentation.fieldWithPath("content").description("게시글 내용").optional()
                        )
                ));
    }
}
  • MockMvc를 통한 테스트 과정과 동일하며, .andDo()에 위 예시와 같이 추가하고 싶은 필드에 대해서 코드를 작성한다. (공식문서 또는 우형블로그참고)

템플릿 만들기

  • src/docs/asciidoc/{파일명}.adoc 경로를 만들어서 작성해야한다.
  • Asciidoc 문법을 사용해야하기 때문에 Asciidoc 사용법 을 참고하며 커스텀할 수 있다.
  • Asciidoc는 Include를 통해 여러 adoc 파일을 가져다 하나의 문서로 만들 수 있다.
= minlog API
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels:2
:sectlinks:

== 글 단건 조회

=== 요청

include::{snippets}/post-inquiry/http-request.adoc[]

include::{snippets}/post-inquiry/path-parameters.adoc[]

//include::{snippets}/post-inquiry/request-fields.adoc[]

=== 응답

include::{snippets}/post-inquiry/http-response.adoc[]

include::{snippets}/post-inquiry/response-fields.adoc[]

== 글 작성

=== 요청

include::{snippets}/post-create/http-request.adoc[]

//include::{snippets}/post-create/path-parameters.adoc[]

include::{snippets}/post-create/request-fields.adoc[]

=== 응답

include::{snippets}/post-create/http-response.adoc[]

//include::{snippets}/post-create/response-fields.adoc[]

Build 실행

  • Test Code에서 .andDo(document("경로" ... 작성한 경로 adoc 파일 생성

  • 템플릿에서 지정한 파일명으로 html 파일 생성

  • 프로젝트 실행 후, localhost:8080/docs/{파일이름}.html 접속


최근에 velog에서 tistory로 넘어가고, Spring Rest Docs에 대해서 정리하였다. 좀 더 깔끔하게 정리하였다고 생각하여 다시 공유해드립니다.
https://minnseong.tistory.com/25/

참고문헌

https://www.inflearn.com/course/%ED%98%B8%EB%8F%8C%EB%A7%A8-%EC%9A%94%EC%A0%88%EB%B3%B5%ED%86%B5-%EA%B0%9C%EB%B0%9C%EC%87%BC/
https://docs.spring.io/spring-restdocs/docs/current/reference/html5/
https://techblog.woowahan.com/2597/

0개의 댓글