Spring REST Docs 소개 및 예제
Spring Rest Docs
생성일: 2021년 1월 5일 오전 9:19
해당 내용은 공식 문서 2.0.5.RELEASE 버전을 참고 및 토대로 작성하였습니다.
Overview
Spring REST Docs 는 RESTful 서비스의 문서화를 도와주는 도구이다. Spring REST Docs 는 문서 작성 도구로 기본적으로 Asciidoctor 를 사용하며, 이것을 사용해 HTML 을 생성한다. 필요한 경우 Markdown 을 사용하도록 변경할 수 있다.
Spring REST Docs 는 Spring MVC 의 테스트 프레임 워크, Spring WebFlux WebTestClient 또는 Rest Assured 3 로 작성된 테스트 코드에서 생성된 Snippet 을 사용한다. 테스트 기반 접근 방식은 서비스 문서의 정확성을 보장해준다. Snippet 이 올바르지 않을 경우 테스트가 실패하기 때문이다.
RESTful 서비스를 문서화 하는 것은 해당 리소스를 설명하는 것이다. 리소스 설명의 핵심은 HTTP 요청과 HTTP 응답의 세부정보이다. Spring REST Docs 를 사용하면 리소스와 HTTP 요청 및 응답을 사용하여 서비스 내부의 세부 정보로부터 문서를 보호할 수 있다. 즉, 서비스 내부 코드에 추가 또는 수정과 같이 영향을 주지 않고도 별도의 문서화 작업을 진행할 수 있게 된다. 이는 서비스 구현보다 API 를 문서화 하는데 도움이 된다. 또한 문서를 재 작업 하지 않고도 버전에 따라 변경할 수 있다.
많이 사용되는 RESTful API 문서도구로는 대표적으로 Swagger 가 있다. 아래 표는 Swagger 와 Spring REST Docs 의 비교 내용이다.
Spring REST Docs | Swagger |
---|---|
코드의 추가 및 수정이 없다. | 코드에 어노테이션등을 추가해야 한다. |
테스트 코드 작성이 필요하며, 테스트 성공 시 문서가 생성된다. | 테스트 코드 없이 서비스 쪽 코드 및 어노테이션 추가로 문서를 생성할 수 있다. |
버전 변화에 유연하고 정확성이 높다. | 버전 변화에 맞춰 재 작성해야 하며 이를 하지 않을 때 정확성이 낮다. |
자세한 내용은 여기를 참고한다.
시작하기
샘플 애플리케이션
여러 샘플 응용 프로그램을 사용할 수 있다.
MockMvc
| Sample | Build System| Description |
————–| — | — |
|Spring Data REST|Maven|Spring Data REST 를 사용|
|Spring HATEOAS|Gradle|Spring HATEOAS 를 사용|
WebTestClient
| Sample | Build System| Description |
————–| — | — |
|WebTestClient|Gradle|Spring WebFlux 의 WebTestClient 를 사용|
REST Assured
| Sample | Build System| Description |
————–| — | — |
|REST Assured|Gradle|Spring WebFlux 의 WebTestClient 를 사용|
고급
| Sample | Build System| Description |
————–| — | — |
|Slate|Gradle|Markdown 및 Slate 를 사용|
|TestNG|Gradle|TestNG 사용|
|JUnit 5|Gradle|JUnit 5 를 사용|
요구 사항
- Java 8 >
- Spring Framework 5 (5.0.2) >
빌드 시스템 설정
-
Maven
<!-- "test" scope 를 가지는 spring-restdocs-mockmvc 의존성 추가 또는 spring-restdocs-webtestclient, spring-restdocs-restassured 추가 --> <dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>{project-version}</version> <scope>test</scope> </dependency> <build> <plugins> <!-- asciidoctor 플러그인 추가 --> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.8</version> <executions> <execution> <id>generate-docs</id> <!-- "prepare-package" 옵션은 패키지내에 API 문서를 포함할 수 있게 한다. --> <phase>prepare-package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html</backend> <doctype>book</doctype> </configuration> </execution> </executions> <dependencies> <!-- "asciidoctor" 의 의존성으로 spring-restdocs-asciidoctor 추가 .adoc 파일이 target/generated-snippets 아래에 생성된 Snippet 을 가리키는 설정이 추가된다. 또한 operation 블록 매크로를 사용할 수 있다. --> <dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-asciidoctor</artifactId> <version>{project-version}</version> </dependency> </dependencies> </plugin> </plugins> </build>
-
Gradle
plugins { // Asciidoctor 플러그인 적용 id "org.asciidoctor.convert" version "1.5.9.2" } dependencies { /* asciidocker 에 대한 spring-restdocs-asciidocker 의존성 추가 Maven 처럼 build/generated-snippets 밑에 생성된 Snippet 을 .adoc 파일이 자동으로 가리키도록 하는 설정 추가. operation 블록 매크로 사용 가능 */ asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' // Maven 과 같이 test Scope 에 대한 mockMvc 의존성을 추가 (WebClient, Assured 사용가능) testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' } ext { // Snippet 의 생성 위치를 지정 snippetsDir = file('build/generated-snippets') } test { // Snippets 디렉토리를 출력으로 작업하도록 설정 outputs.dir snippetsDir } asciidoctor { // Snippets 디렉토리를 Input 디렉토리로 설정 inputs.dir snippetsDir // 문서 생성 전 테스트가 실행되도록 test 에 종속 설정 dependsOn test }
문서 패키징
생성된 API 문서를 jar 또는 war 에 패키징하기 위한 설정이다. 이는 Spring Boot 에 의해 정적 콘텐츠로 제공된다.
- jar 가 빌드되기 전에 문서가 생성.
- 생성된 문서는 jar 에 포함.
-
Maven
<plugin> <!-- Asciidoctor 플러그인 선언 --> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> </plugin> <plugin> <!-- 문서가 패키지로 복사되기 전에 생성되야 하므로 위에 Asciidoctor 플러그인 뒤에 선언한다. --> <artifactId>maven-resources-plugin</artifactId> <version>2.7</version> <executions> <execution> <id>copy-resources</id> <phase>prepare-package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <!-- 생성된 문서를 static/docs 에 복사 --> <outputDirectory> ${project.build.outputDirectory}/static/docs </outputDirectory> <resources> <resource> <directory> ${project.build.directory}/generated-docs </directory> </resource> </resources> </configuration> </execution> </executions> </plugin>
-
Gradle
bootJar { // 빌드 전 문서 생성 확인 dependsOn asciidoctor // 생성된 문서를 static/docs 에 복사 from ("${asciidoctor.outputDir}/html5") { into 'static/docs' } }
문서 조각 생성
Spring REST Docs 는 테스트 프레임워크, WebTestClient 등을 사용해서 문서화에 필요한 서비스에 요청을 한다. 그 다음 요청 및 결과 응답에 대한 문서 Snippet 을 생성한다.
테스트 설정
테스트 프레임워크에 따라 다르다. 여기에서는 JUnit 5 에 대한 설정을 다룬다.
- JUnit 5 테스트 설정
-
RestDocumentationExtension 을 테스트 클래스에 적용한다.
@ExtendWith(RestDocumentationExtension.class) public class JUnit5ExampleTests {}
RestDocumentationExtension 은 Maven 의 경우 “target/generated-snippets” Gradle 의 경우 “build/generated-snippets” 를 자동으로 출력 디렉토리로 설정되어있다.
-
일반적인 Spring Application 의 경우에는 SpringExtension 을 적용한다.
@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) public class JUnit5ExampleTests {}
-
JUnit 5.1 을 사용하는 경우 클래스의 필드로 등록해서 출력 디렉토리를 임의로 지정할 수 있다.
public class JUnit5ExampleTests {
@RegisterExtension
final RestDocumentationExtension restDocumentation = new RestDocumentationExtension ("custom");
}
다음은 MockMvc 를 사용한 설정 방법이다.
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
private MockMvc mockMvc;
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
documentationConfiguration 은 기본값이 있지만 사용자 정의를 위한 다양한 설정도 제공한다. 내용은 여기를 참고한다.
추가로 alwaysDo() 메서드에서 문서화로 보여줄 때 이쁘게 보여주기 위한 옵션도 추가한다.
JUnit 없이 테스트 설정
- JUnitRestDocumentation 대신 ManualRestDocumentation 을 사용
- @Rule 어노테이션 제거
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
private ManualRestDocumentation restDocumentation = new ManualRestDocumentation();
@BeforeMethod
public void setUp(Method method) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}
마지막으로 각 테스트 후에 ManualRestDocumentation.afterTest 를 호출해야 한다.
@AfterMethod
public void tearDown() {
this.restDocumentation.afterTest();
}
RESTful 서비스 호출
-
RESTful 호출 예제
// 서비스의 루트("/") 로 요청하고 응답 타입은 application/json 으로 한다. this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) // 응답 확인 .andExpect(status().isOk()) // 서비스 호출을 문서화 하는데 Snippet 이름을 index 로 지정. // Snippet 은 "RestDocumentationResultHandler" 에 의해 작성되며, 클래스의 인스턴스는 // "org.springframework.restdocs.mockmvc.MockMvcRestDocumentation" 의 "document" // 메서드를 참고한다. .andDo(document("index"));
기본적으로 6개의 Snippet 이 작성된다. 그 외에 Snippet 에 대한 정보는 여기를 참고한다.
<output-directory>/index/curl-request.adoc
<output-directory>/index/http-request.adoc
<output-directory>/index/http-response.adoc
<output-directory>/index/httpie-request.adoc
<output-directory>/index/request-body.adoc
<output-directory>/index/response-body.adoc
Snippet 사용
테스트 후 생성된 Snippet 을 사용하기위해 .adoc 파일을 생성해야 한다. 파일명은 아무렇게해도 상관없다. 그리고 생성된 snippet 을 연결해주면 된다.
include::{snippets}/index/curl-request.adoc[]
만들어 놓은 .adoc 과 생성된 Snippet 의 연결이 끝났다면 maven install 또는 gradle build 로 .html 파일을 생성해준다.
빌드도구 | 소스 파일 | 생성 된 파일 |
---|---|---|
Maven | src/main/asciidoc/*.adoc | target/generated-docs/*.html |
Gradle | src/docs/asciidoc/*.adoc | build/asciidoc/html5/*.html |
Simple Example
코드는 여기를 참고 한다.
의존성 추가
- spring-boot-starter-web 과 Spring REST Docs 를 위한 spring-restdocs-mockmvc 를 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
- Plugin 설정
- Asciidoctor 플러그인을 설정
- 생성 된 Snippet 과 연결된 .adoc 을 빌드 했을 때 만들어지는 HTML 파일의 위치 설정
- HTML 파일을 복사하여 웹에서 확인할 수 있는 디렉토리로 이동
- maven-resources-plugin 은 Snippet 이 생성된 이후에 동작(파일 복사) 되어야 하므로 asciidoctor-maven-plugin 뒤에 오도록한다.
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<attributes>
<snippets>${snippetsDirectory}</snippets>
</attributes>
<sourceDirectory>${basedir}/src/main/docs/asciidocs</sourceDirectory>
<outputDirectory>${project.build.directory}/generated-docs</outputDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${basedir}/src/main/resources/static/html/docs
</outputDirectory>
<resources>
<resource>
<directory>
${project.build.directory}/generated-docs
</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
예제 Controller, Service, DTO 추가
-
DTO
@Data public class RestDocsTest { private String id; private String name; private String body; }
-
Service
@Service public class RestDocsTestService { public ResponseEntity<RestDocsTest> readContent(RestDocsTest restDocsTest) { RestDocsTest result = new RestDocsTest(); try { result.setId("ID"); result.setName("Name"); result.setBody("Body"); return new ResponseEntity<>(result, HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } }
-
Rest API Controller
@RestController @RequestMapping("/test") public class RestDocsRestController { private final RestDocsTestService restDocsTestService; public RestDocsRestController(RestDocsTestService restDocsTestService) { this.restDocsTestService = restDocsTestService; } @GetMapping(value = { "" }) public ResponseEntity<RestDocsTest> readRestDocsTest(@RequestBody(required = false) RestDocsTest restDocsTest) { ResponseEntity<RestDocsTest> result = restDocsTestService.readContent(restDocsTest); return result; } }
포트 설정(필요 시) 및 View Resolver 확장자 설정
server:
port: 8080
spring:
mvc:
view:
suffix: .html
테스트 코드 작성
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class })
@SpringBootTest(classes = ExampleSprintRestDocsApplication.class)
class ExampleSprintRestDocsApplicationTests {
private MockMvc mockMvc;
@Test
void contextLoads() {
}
@BeforeEach
public void setUp(
WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentationContextProvider) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentationContextProvider))
.alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
@Test
public void readExample() throws Exception {
this.mockMvc.perform(get("/test"))
.andExpect(status().isOk())
.andDo(
document("test-example",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
PayloadDocumentation.responseFields(
PayloadDocumentation.fieldWithPath("id")
.description("ID 필드 입니다."),
PayloadDocumentation.fieldWithPath("name")
.description("이름 필드 입니다."),
PayloadDocumentation.fieldWithPath("body")
.description("데이터 필드 입니다.")
)));
// links(
// // linkWithRel 은 텍스트를 링크화 한다.
// linkWithRel("test").description("The Test resource")),
// //subsectionWithPath 를 사용해서 메서드에 대한 응답을 문서화 했다.
// responseFields(subsectionWithPath("_links")
// .description("Links to other resources")),
// // 응답에 "Content-type" 헤더를 추가하기 위해 headerWithName 을 사용
// responseHeaders(headerWithName("Content-Type")
// .description("The Content-Type of the payload, e.g. `application/hal+json`")))
}
}
빌드 및 Snippet 확인
만들어진 Snippet 들을 연결해줄 사용자 정의 .adoc 파일을 “src/main/docs/aciidocs” 경로에 만들고 다음과 같이 작성한다.
= RESTful Notes API Guide
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:
include::{snippets}/test-example/curl-request.adoc[]
include::{snippets}/test-example/http-request.adoc[]
include::{snippets}/test-example/http-response.adoc[]
include::{snippets}/test-example/httpie-request.adoc[]
include::{snippets}/test-example/request-body.adoc[]
include::{snippets}/test-example/response-body.adoc[]
include::{snippets}/test-example/response-fields.adoc[]
이제 Snippet 들을 만들기 위한 빌드를 실행한다.
mvn clean install
빌드 및 테스트가 완료되면 target/generated-snippets 밑에 기본 파일들이 생긴다. 해당 Snippet 들은 자동으로 .adoc 과 연결된다. 또한 static/html/docs 밑에 html 이 생성된 것을 볼 수 있다.
HTML 웹 페이지 연결
이제 만들어진 HTML 을 웹 페이지로 볼 수 있게 연결해보자.
@Controller
@RequestMapping("/docs")
public class RestDocsController {
@GetMapping
public String docs() {
return "html/docs/rest-test-api-guide";
}
}
웹 페이지 확인
스프링 부트 애플리케이션을 실행하고 다음 주소로 접속하면 Spring REST Docs 로 만들어진 문서를 볼 수 있다.
http://localhost:8080/docs
댓글남기기