Home Spring Rest Docs 적용하기
Post
Cancel

Spring Rest Docs 적용하기

스프링 웹 애플리케이션에서 Controller 계층 단위 테스트에 RestDocs 적용하기

REST API 문서의 필요성

API 스펙에 대한 명세서(문서)는 개발시에 반드시 필요하다. API문서는 요청 할 수 있는 API와 필요 파라미터 정보등이 기술되어있고 또 어떤 응답을 반환하는 지에 대한 정보를 정리해놓은 문서이다.

API문서가 존재하지않으면 API를 사용하는 사람이 직접 백엔드 코드를 보면서 어떻게 API를 사용하는지에 대해 파악해야한다.
또 API문서는 약속된 API 스펙에 대한 정보를 담고 있으므로, API 기능 구현이 다 되어있지 않더라도 우선 먼저 문서를 만들고 협업부서간 공유를 해서 동시에 여러팀이 개발을 진행할 수 있다.

API 문서 관리의 어려움

API 문서는 정해진 양식이 크게 중요하지는 않다. 서로 약속된 포맷하에 엑셀이든, 사내 wiki문서든 정리만 되어있으면 된다.
하지만 API 문서는 관리가 쉽지 않다. 소스코드를 변경해서 API 스펙 변경이 있다면 문서도 따로 그떄 그때 수정을 해줘야한다.
소스 코드 따로 문서 따로 이기때문에 관리가 잘되지않으면 어느세 어긋나게 되면서 제대로 역할을 하지 못할 수가 있다.

API 문서 자동화 - Swagger, Spring Rest Docs

위에서 언급한 어려움을 해결 할 수 있도록 소스코드 기반으로 API 문서를 자동으로 생성해주는 도구가 있다. Java Spring 기반 Web application에서는 Swagger와 Spring Rest Docs가 주로 사용된다. 각 도구별 장단점을 비교해보면 아래와 같다.

 Spring Rest DocsSwagger
장점테스트가 성공해야 문서가 만들어진다.API 테스트를 실행할 수 있는 UI 제공
 Production Code와 독립적이다.적용하기 쉽다.
단점적용하기 어렵다.Production Code에 문서를 위한 어노테이션을 추가해야한다.
 테스트 코드에 문서화를 위한 코드가 많이 필요하다.검증되지 않은 API가 생성될 수 있다.

Rest Docs 설정

build.gradle에 dependencies에 사용할 version만 추가해주면 쉽게 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
plugins { // (1)
	id "org.asciidoctor.jvm.convert" version "3.3.2" 
}

configurations { // (2)
	asciidoctorExt  
}

dependencies { // (3)
	asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' 
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' 
}

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

test { // (5)
	outputs.dir snippetsDir
}

asciidoctor { // (6)
	inputs.dir snippetsDir 
	configurations 'asciidoctorExt' 
	dependsOn test 
}

bootJar { // (7)
	dependsOn asciidoctor
	from("${asciidoctor.outputDir}"){
		into 'static/docs'
	}
}

(1) asciidoctor plugin 추가
(2) spring-restdocs-asciidoctor를 위한 설정으로 자동으로 .adoc에서 사용하는 snippets 변수가’build/generated-snippets’이 되도록 자동 설정해준다.
(3) spring-restdocs-asciidoctor는 생성된 .adoc snippet파일들을 묶어서 하나의 asciidoc(html)으로 만들어주는 라이브러리다. spring-restdocs-mockmvc는 mockmvc를 사용해서 rest docs를 .adoc snippet파일들을 생성해주는 라이브러리다.
(4) spring-restdocs-mockmvc를 통해서 생성될 .adoc snippet 파일들을 저장할 경로에 대한 변수 지정
(5) test task의 output(adoc snippet파일들) 경로 지정
(6) asciidoctor task 지정 - test task의 output을 input으로 받아서 asciidoctorExt 설정을 적용하고 asciidoc을 만든다.
(7) packaging시에 asciidoctor task의 output 폴더에 위치한 html5 파일들을 build된 jar file내에서 static/docs 경로에 패키징 한다. jar 파일 실행 후 hostname/docs/index.html 접속 시 Rest Docs 확인할 수 있다.

MockMVC가 아닌 WebTestClient나 REST Assured를 사용하면서 rest docs를 만들고 싶다면 spring-restdocs-webtestclient 또는 spring-restdocs-restassured 의존을 추가하면 된다.

Base Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@ExtendWith(RestDocumentationExtension.class)
public class ControllerWithDocumentTest {
    
    protected MockMvc mockMvc;

    @Autowired
    protected ObjectMapper objectMapper;

    @BeforeEach
    public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider provider) {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .apply(documentationConfiguration(provider))
                .build();
    }
    ...
}
  • @ExtendWith(RestDocumentationExtension.class)
    • Spring REST Docs에서 제공하는 익스텐션 적용
    • 테스트 코드 시에 수행되는 요청과 응답에 대한 정보를 캡쳐해서 Asciidoc 파일을 만들어 냄
  • MockMvc
    • 웹 어플리케이션을 서버에 배포하지 않고 테스트용 MVC환경을 만들어 요청 및 전송, 응답기능을 제공해주는 유틸리티
  • ObjectMapper
    • Java Object <-> Json Data 간 변환을 위한 유틸리티
  • setUp()
    • 테스트 코드 마다 MockMvc 객체를 셋업해주는 함수 MockMvc 인스턴스를 통해 rest docs의 결과를 만들수 있도록 설정

Controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequiredArgsConstructor
public class PostController {

    private final PostSerivce postService;

    @PostMapping("/api/v1/posts")
    public PostResposne createPost(@Valid @RequestBody PostCreateRequest request){
        return postService.createPost(request);
    }

}

endpoint가 /api/v1/posts이고, method가 Post인 createPost에 대한 테스트 코드를 작성해보자.

Test Class Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@WebMvcTest(controllers = PostController.class)
class PostControllerWithDocumentTest extends ControllerWithDocumentTest {

    @DisplayName("게시글 생성 요청시, 게시글을 저장한다.")
    @Test
    void createPost(){
        // given
        PostCreateRequest request = PostCreateRequest.builder()
                .title("테스크 코드 작성법")
                .content("본문 내용")
                .author("sehyeong")
                .build();

        PostCreateResponse expected = PostCreateResponse.builder()
                .id(1L).build();

        given(postService.createPost(request)).willReturn(expected);

        // when
        ResultActions resultActions = mockMvc.perform(
                        post("/api/v1/posts")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(objectMapper.writeValueAsString(request)))
                .andDo(print())
                .andDo(document("post-create", // Rest Docs 생성을 위한 코드
                                preprocessRequest(prettyPrint()),
                                preprocessResponse(prettyPrint()),
                                requestFields(
                                        fieldWithPath("title").type(JsonFieldType.STRING)
                                                .description("글 제목"),
                                        fieldWithPath("content").type(JsonFieldType.STRING)
                                                .description("글 내용"),
                                        fieldWithPath("author").type(JsonFieldType.STRING)
                                                .description("작성자")
                                ),
                                responseHeaders(
                                        headerWithName("Location").description("생성된 Post URL")
                                )
                        )
                );

        // then
        resultActions
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", "/api/v1/posts/1"));

    }
}

이전글에서 다룬 controller 계층 테스트 코드와 거의 흐름이 같다. 다른점은 Rest Docs 생성을 위한 andDo(document(~)); 부분이 추가 되었다. API 스펙을 명시하는 document 함수부분에서 사용할 수 있는 api들은 공식 문서를 참고하자.

Build를 돌려보면 .adoc 파일들이 생성된다.

Desktop View test 수행 후 생성된 .adoc snippets 파일들

build.gradle에서 test task의 outputs.dir로 build/generated-snippets 경로를 지정해주었기떄문에, 테스트 코드가 수행되고 해당 경로에 .adoc 파일이 생성된다.

.adoc 파일을 묶어서 asciidoc 생성하기

Desktop View asciidoctor가 위에서 생성된 .adoc snippets을 묶기 위한 adoc 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
= Blog Demo REST API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

[[Post-API]]
== Post API

[[Post-create]]
=== Post-create

==== HTTP Request
include::{snippets}/post-create/http-request.adoc[]
include::{snippets}/post-create/request-fields.adoc[]

==== HTTP Response
include::{snippets}/post-create/http-response.adoc[]
include::{snippets}/post-create/response-headers.adoc[]

.adoc snippets 파일을 묶어서 하나의 docs 파일로 만들기 위해서 src/docs/asciidoc/경로에 snippets 파일들을 import하는 메인 adoc 파일을 생성한다.

Desktop View asciidoctor가 생성한 하나의 도큐멘트 파일(HTML5)

다시 build를 돌려보면 build/docs/asciidoc/ 경로에 index.html 파일이 생긴것을 확인할 수 있다. asciidoctor가 src/docs/asciidoc/에 있는 adoc 파일을 가지고 asciidoc을 만들기 때문에 이전 과정에서는 해당 경로에 adoc 파일이 없었기 떄문에 생성되지 않은 것이다.

Desktop View

생성된 index.html 파일을 열어보면 문서가 생성이 잘 됐음을 확인 할 수 있다.

build.gradle에 bootJar 부분에 asciidoctor의 결과물을 static/docs로 넣도록 했기때문에 build 결과물인 jar파일로 스프링부트 서버를 실행하고 localhost:8080/docs/index.html 경로로 접속해보면 Rest Docs문서 역시 배포가 잘 되는 것을 확인할 수 있다.

더 나아가기 1 - 커스텀 하기

Spring Rest Docs이 설정이 복잡하고 코드를 추가해줘야 하는 단점이 있지만, 문서를 원하는대로 커스텀화 할 수 있는 장점도 있다. Resquest Param의 필수값 여부나, 기본값에 대한 설명과 같은 부가 정보들에 대한 정보 역시 추가할 수 있다. 해당 내용은 아래 우아한형제들의 기술블로그를 참조하고 공식문서를 바탕으로 커스텀해보자.

더 나아가기 2 - OpenAPI Specification으로 Rest Docs + Swagger 도 가능하다.

OpenAPI Specification(OAS)은 HTTP API / RESTful API를 json 또는 yaml 형식으로 정의/기술하는 표준 인터페이스이다.
Swagge에서 사용되던 표준이었으나 현재는 모든 RESTful API 스펙에 대한 표준으로 활용되고 있고, 현재는 linux foundation의 오픈소스 프로젝트이다.

Spring Rest Docs를 통해 API 문서도 만들고,
restdocs-api-spec이라는 오픈소스를 가지고 OpenAPI Specification에 맞게 json/yaml 파일도 만들어 낼 수 있다. 이 OpenAPI Specification json/yaml파일을 가지고 Swagger ui를 만들어낼 수 있다.
깔끔한 명세서인 Spring Rest Docs도 만들면서, Swagger로 API 테스트 실행까지 해볼 수 있다.
아래 문서와 영상을 참고해보자.

Refercence

This post is licensed under CC BY 4.0 by the author.

테스트 코드의 역할과 필요성

스프링에서 Redis 사용하기