Spring boot IntegrationTest - https://galid1.tistory.com/735
이번 포스팅에서는 지난 포스팅에서 작성한 Integeration Test에서 Spring Restdocs 설정을 통해 API 문서를 만들어 보도록 하겠습니다.
code (gitlab) - https://gitlab.com/galid1/spring-boot-integration-test
완성된 코드는 위의 주소에서 확인 가능합니다.
1. Spring RestDocs 설정
build.gradle
의존성 설정
x...
dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
...
}
...
spring rest-docs 의존성을 추가합니다.
Asciidoctor converter plugin 추가
xxxxxxxxxx
plugins {
...
id 'org.asciidoctor.convert' version '1.5.6'
}
...
asciidoctor converter 플러그인을 추가합니다.
Asciidoctor task 추가
xxxxxxxxxx
...
// API 문서(html) 생성 Task
asciidoctor {
sourceDir 'src/main/asciidoc'
attributes \
'snippets': file('target/snippets')
}
...
위와 같이 Asciidoctor task를 추가합니다.
위의 task는, test 코드를 통해 생성된 snippets
와 아래에서 만들 api 문서 구조를 정의하는 index.adoc
를 이용해, html
를 생성하는 task 입니다.
index.adoc 추가
이제, 위의 gradle에서 정의한 API 문서 생성 task
에서 문서 구조를 정의하기위해 사용될 index.adoc를 생성해야합니다.
위의 task의 sourceDir
에서 정의한 위치(src/main/asciidoc/)에 index.adoc를 생성합니다. (asciidoc는 원래 없는 디렉토리 이므로 임의로 생성해야합니다.)
API 문서 생성 테스트
여기까지 진행했다면, API 문서를 만들 준비는 다되었습니다. 우선, 자동으로 문서가 생겨나는지를 테스트 해보도록 하겠습니다.
index.adoc 편집
xxxxxxxxxx
= User App API
== API 문서 자동생성
src/main/index.adoc
를 열어 위와 같이 작성합니다.
asciidocs 문서를 작성하는 방법은 위 링크를 통해 확인할 수 있습니다.
Asciidoctor task 실행
터미널을 열어 ./gradlew asciidoctor
을 입력해 앞서 생성한 task를 실행합니다.
성공한다면, 위와 같이 BUILD SUCCESSFUL
이란 메시지가 출력됩니다.
$PROJECT$/build/asciidoc/html/ 하위 디렉토리를 확인하면, index.html이 생성되어있는 것을 볼수있습니다. path copy를 통해 확인해봅니다.
성공입니다.
2. TEST 코드에 Snippets 생성 코드 추가
Spring RestDocs는 테스트 코드를 통해, 해당 API의 규격을 담고있는 snippets
라는 것을 생성하게됩니다. 그 후, asciidocs에서 생성된 snippets를 이용해, API 문서를 생성합니다.
예를들어 snippets에는 위와 같은 내용이 담겨있습니다. 이러한 snippets는 말씀드린것과 같이 API 요청에대한 정보, 응답 정보, 등등의 정보를 가지고 있습니다.
https://docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/
이제 API 문서 생성에 사용될 여러가지 snippets를 생성하는 방법을 알아보도록 하겠습니다. 제가 아래에 설명드리는 여러가지 snippets 생성 방법 외에도 다양한 생성 방법은 위 공식 문서 링크를 통해 확인 가능합니다.
2.1 Test Code 설정 추가
Snippets 생성 코드를 추가하기 앞서, snippets를 생성할때 필요한 설정들을 진행해야합니다. 지난 포스팅에서 통합테스트의 부모 클래스로 사용하기위해 작성한 BaseIntegrationTest
클래스를 수정합니다.
@AutoConfigureRestDocs()
Spring boot에서 RestDocs 설정을 자동으로 진행하도록 돕는 어노테이션입니다. 문서생성에 사용될 snippets이 생성될 위치, 해당 api의 uri에 관련된 설정들을 진행할 수 있습니다. ouputDir는 asciidoctor task 설정시 선언한 snippets의 위치를 지정합니다.
2.2 테스트 코드에 Snippets 생성 코드 추가
유저생성 API 생성을 위한 Snippets 만들기
xxxxxxxxxx
public void 유저생성() throws Exception {
//given
String USER_NAME = "TEST";
CreateUserRequest createRequest = new CreateUserRequest(USER_NAME);
//when
ResultActions resultActions = mvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createRequest))
.accept(MediaType.APPLICATION_JSON))
.andDo(print());
//then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("userId", is(notNullValue())));
}
지난 포스팅에서 작성한 test code입니다.
xxxxxxxxxx
//when
ResultActions resultActions = mvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createRequest))
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andDo(document("user/{method-name}"));
우선 위와 같이 andDo() 메소드의 매개변수로 document("user/{method-name}")을 전달합니다. document() 메소드는 Spring Restdocs에서 제공하는 static 메소드로, API 문서 생성에 필요한 snippets를 생성하도록 하는 메소드 입니다. 메소드의 매개변수로 전달된 {method-name}
의 경우, 실행되는 testcode 메소드의 이름에 따라 동적으로 디렉토리를 생성하여 그안에 snippets를 생성합니다.
target/snippets/user/메소드이름
의 경로에 snippets 들이 생성됨을 볼 수 있습니다.
xxxxxxxxxx
= User App API
== User
=== /users
==== 유저 생성
.request
include::{snippets}/user/유저생성/http-request.adoc[]
.response
include::{snippets}/user/유저생성/http-response.adoc[]
src/main/asciidoc/index.adoc
를 위와같이 수정합니다.
터미널에서 ./gradlew asciidoctor
을 실행합니다.
PROJECT/build/asciidoc/html5/
경로의 index.html을 열면 위와 같이, API를 확인할 수 있습니다. 하지만 이렇게 API간단하다면, 클라이언트가 고생할것입니다. 각각의 request, response에 대한 추가 설정을 진행하도록 하겠습니다.
request, response 각각의 필드 설명 snippets 생성하기
xxxxxxxxxx
public void 유저생성() throws Exception {
//given
...
//when
ResultActions resultActions = mvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createRequest))
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andDo(document("user/{method-name}",
requestFields(
fieldWithPath("userName").description("유저의 이름")
),
responseFields(
fieldWithPath("userId").description("Database 상의 user_id")
)
));
...
}
document() 메소드는 인자로, identifier, snippets
또는 identifier, operationRequestProcessor, snippets
, 또는 identifier, operationResponseProcessor, snippets
를 받습니다. (operationRequestProcessor, operationResponseProcessor
은 뒤에서, 추가 설정에서 설명드리고, 지금은 document(identifier, snippets)를 이용하도록 하겠습니다.)
requestFields() 의 경우, FieldDescriptor를 매개변수로 받아 requestFieldsSnippet를 생성합니다. 간단히 말씀드려, 필드를 설명하는 내용들을 매개변수로 받아, RequestFieldsSnippets를 생성하도록 합니다.
따라서 document의 매개변수로 , requestFields()를, 다시 requestFields()의 매개변수로, 각각의 요청 데이터의 필드들을 설명하는, fieldWithPath().description()을 넘겨주면 됩니다.
다시 테스트코드를 실행하면, 전에는 보이지 않던 request-fields.adoc와 response-fields.adoc가 보입니다.
xxxxxxxxxx
= User App API
== User
=== /users
==== 유저 생성
.request
include::{snippets}/user/유저생성/http-request.adoc[]
include::{snippets}/user/유저생성/equest-fields.adoc[]
.response
include::{snippets}/user/유저생성/http-response.adoc[]
include::{snippets}/user/유저생성/response-fields.adoc[]
index.adoc
에 위와 같이 방금 생성한 snippets를 사용하는 코드를 추가하고, asciidoctor
gradle task를 실행합니다.
요청, 응답 각각의 필드들을 설명하는 내용이 추가되었습니다.
https://gitlab.com/galid1/spring-boot-integration-test
이제 마찬가지로 나머지 테스트 코드에, document()를 추가하여 적절한 snippets 생성 코드를 작성합니다. 완성된 코드는 위의 링크에서 볼 수 있습니다.
3. 추가 설정
본문 format
완성된 API를 보면, request, response의 본문이 일자로 출력되는 것을 볼 수 있습니다.
이는, 생성되는 snippets의 모양이 저형태로 생성되기 때문입니다. 그렇다면 이것을 json 형태에 맞추어 formatting 해주는 기능은 없을까요 ???
document()라는 오버로딩된 메소드중 identifier, snippets 외, OperationResponseProcessors라는 매개변수를 추가적으로 받는 메소드가 존재합니다.
xxxxxxxxxx
...
.andDo(document("user/{method-name}",
preprocessRequest(prettyPrint()), // format
preprocessResponse(prettyPrint()), // format
requestFields(
fieldWithPath("userName").description("유저의 이름")
),
responseFields(
fieldWithPath("userId").description("Database 상의 user_id")
)
));
...
이 매개변수는 snippets를 처리하는 방법을 다룹니다. 이 메소드의 매개변수로 prettyPrint()를 전달합니다.
위와 같이 snippets 이 format 되어 출력됩니다.
asciidoctor task를 다시 실행하고, 문서를 확인하면, 위와같이 예쁘게? 출력된 응답 본문을 확인할 수 있습니다.
자동 목차 만들기 (Table Of Contents: toc)
API의 목차를 수정하기란 여간 귀찮은 일이 아닐 수 없습니다. api의 경로 수정 -> 목차의 경로수정.. 반복... word에서 자동 목차 생성기능이 제공 되기는 하지만, 온라인 버전에서는 제공되지 않기도하고, restdocs를 사용하고 있는 입에서, 자동으로 목차를 생성해주는 기능은 없을까? 라고 생각하다가 간단히 :toc:
를 입력하는 것으로 목차를 생성해주는것을 발견했습니다.
index.adoc에, 위와 같이 설정하면, =(h1), ==(h2) ...
등을 각각의 인덱스로 하여 자동으로 목차를 만들어줍니다. toclevels
의 경우, 목차에 표현될 =
의 단계를 지정합니다.
다시 asciidoctor task를 실행하고 확인하면,.. 위와 같이 목차가 생겨난것을 확인할 수 있습니다.
좌측 목차 만들기
맨 위에만 존재하는 목차보다는 좌측에 고정된 목차를 원할수도 있습니다. 이 또한 제공이됩니다. :toc: left
라고, 위와 같이 입력하면 끝입니다.
중요한점
=
가 설정된 바로 밑에 :toc:
를 작성해야합니다. 별도로 개행을 한다거나, 다른 문자등이 사이에 있다면 인식을 하지 않습니다.
또한, =
바로 밑이 아닌, 다른 위치에 입력해도 안됩니다.
Application과 같이 배포하기
asciidoctor {
sourceDir 'src/main/asciidoc'
outputDir 'src/main/resources/static'
attributes \
'snippets': file('target/snippets')
}
bootJar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'src/main/resources/static/docs'
}
}
1. asciidoctor task에 outputDir을 추가합니다, 'static/'으로 하는 지정하는 경우, src와 같은 레벨에 파일이 생기므로 경로를 모두 적어야합니다. (다른 블로그는 모두 'static/...'으로 하였는데 ... gradle설정이 조금 다른것 같다.)
2. bootJar task를 위와 같이 작성합니다.
dependsOn은 asciidoctor task에 의존성을 부여하여 해당 task가 실행된 뒤 bootJar task가 실행되도록 보장합니다.
from(...) { into ...} 의 경우, asciidoctor에 지정한 "outputDir/html5"를, "src/main/resources/static/docs"로 이동시킵니다.
'FrameWork > Spring Boot' 카테고리의 다른 글
Spring Boot - Mockito를 이용해 외부라이브러리를 이용하는 서비스 테스트하기 (0) | 2020.06.26 |
---|---|
Spring Boot - FCM Push 서버 구축하기 (15) | 2020.06.12 |
Spring Boot - 스프링 부트 통합테스트 방법과 팁(Spring boot Integration Test) (2) | 2020.05.29 |
Spring Boot -Spring boot 2.2.5 File upload 문제 (stream ended unexpectedly, Required request part is not present) (0) | 2020.05.12 |
Spring Boot - Custom Validator를 생성해야하는 경우와 생성방법(Collection 검증) (0) | 2020.04.10 |