Spring Rest Docs
장점
- 운영중인 코드에 영향을 미치지 않음
- 변경된 기능에 대해서 최신문서 유지가 어느정도 가능함
- Test케이스 실행 -> 문서를 자동으로 생성해줌
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
group = 'com.tony'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
asciidoctorExt
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
asciidocVersion = "2.0.6.RELEASE"
snippetsDir = file('build/generated-snippets')
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.querydsl:querydsl-core'
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:${asciidocVersion}"
testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:${asciidocVersion}"
}
tasks.named('test') {
useJUnitPlatform()
}
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"
}
}
asciidoctor task를 통해서 만들어진 adoc 파일을 statc/docs 로 옮겨서 localhost:8080/docs/index.html 으로 get요청을 날렸을시
이런식으로 조회가 가능하다.
@SpringBootTest
@ExtendWith(RestDocumentationExtension.class)
public class PostControllerDocTest {
private MockMvc mockMvc;
@Autowired
private PostRepository postRepository;
@BeforeEach
void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
@DisplayName("글 단건 조회 테스트")
void test1() throws Exception {
// given
Post post = Post.builder()
.title("123456789012345")
.content("bar")
.build();
postRepository.save(post);
// expected
mockMvc.perform(MockMvcRequestBuilders.get("/posts/{postId}",1).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcRestDocumentation.document("index"));
}
}
통과한 test를 기반으로 rest docs api 문서를 만들게된다.
build/generated-snippets/index 에 여러 adoc파일들이 생기게 된다.
= 블로그 API
:toc:
== 글 단건 조회
=== 요청
include::{snippets}/index/http-request.adoc[]
=== 응답
include::{snippets}/index/http-response.adoc[]
=== CURL
include::{snippets}/index/curl-request.adoc[]
만들어진 adoc 파일들을 기반으로 src/docs/asciidoc/index.adoc 파일에 만들어진것들을 import하고 꾸미민후에
bootjar task를 통해서 static 폴더 밑으로 index.html이 생성되게 된다.
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriScheme = "https", uriHost = "api.blog.com",uriPort = 443)
@ExtendWith(RestDocumentationExtension.class)
public class PostControllerDocTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private PostRepository postRepository;
@BeforeEach
void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.build();
}
localhost, 같은 주소로 나오지 않게 @AutoConfigureRestDocs를 통해 원하는 포멧을 지정하였다.
기존에는 MockMvc를 setup에서 만들어서 주입하였지만 @AutoConfigureMockMvc를 통해 스프링에서 @AutoConfigureRestDocs가 반영된 MockMvc를 주입할 수 있도록 변경하였다.
변경이 된것을 확인
응답필드에 대한 설명이 부족하다. Path parameter 인 postId에 대한 설명과 repsonse필드에 대한 설명을 추가해보자
@Test
@DisplayName("글 단건 조회 테스트")
void test1() throws Exception {
// given
Post post = Post.builder()
.title("123456789012345")
.content("bar")
.build();
postRepository.save(post);
// expected
mockMvc.perform(RestDocumentationRequestBuilders.get("/posts/{postId}",1).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcRestDocumentation.document("index",
RequestDocumentation.pathParameters(
RequestDocumentation.parameterWithName("postId").description("게시글 ID")
),
PayloadDocumentation.responseFields(
PayloadDocumentation.fieldWithPath("id").description("게시글 ID"),
PayloadDocumentation.fieldWithPath("title").description("제목"),
PayloadDocumentation.fieldWithPath("content").description("내용")
)
));
}
RestDocumentationRequestBuilder의 get을 써야 RequestDocumentation의 메소드들을 정상적으로 사용할 수 있다. MockMvcRequestBuilders 랑 동일한 클래스를 리턴하기에 문제없이 쓸 수 있다.
중간에 "content" 에 대한 description을 빼먹는다면 해당 테스트는 repsonse field 중에 id,title 은 설명이 추가되어있는데 content에 대한 설명은 빠져있다고 오류를 내뱉는다.
= 블로그 API
:toc:
== 글 단건 조회
=== 요청
include::{snippets}/index/http-request.adoc[]
=== 요청 파라메터
include::{snippets}/index/path-parameters.adoc[]
=== 응답
include::{snippets}/index/http-response.adoc[]
include::{snippets}/index/response-fields.adoc[]
=== CURL
include::{snippets}/index/curl-request.adoc[]
추가로 생성된 adoc을 index.adoc에 추가해주면된다.
Spring REST Docs
Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test, WebTestClient, or REST Assured.
docs.spring.io
에 나온것처럼 2가지 방법으로 customzing할 수 있다.
@DisplayName("글 등록")
void test2() throws Exception {
// given
PostCreate request = PostCreate.builder()
.title("호돌맨")
.content("반포자이")
.build();
String json = objectMapper.writeValueAsString(request);
// expected
mockMvc.perform(post("/posts")
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.content(json))
.andDo(print())
.andExpect(status().isOk())
.andDo(document("post-create",
requestFields(
fieldWithPath("title").description("제목")
.attributes(key("constraint").value("좋은제목 입력해주세요.")),
fieldWithPath("content").description("내용").optional()
)
));
}
attribute() , optional() 같은 메소드를 추가하는 방법과
|===
|Path|Type|Description|Optional|Constraint
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{#optional}}Y{{/optional}}
|{{#constraint}} {{.}} {{/constraint}}
{{/fields}}
|===
org/springframework/restdocs/templates/asciidoctor/request-fields.snippet 에 orverride할 snippet을 적어주면 원하는대로 덮어 씌워줄 수 있다.