Spring Boot Restdocs でドキュメント作成
Restdocs および asciidocs を使って、単体テストの結果から API 設計書を自動作成します。
cf. https://spring.pleiades.io/guides/gs/testing-restdocs
各種ファイルサンプル
サンプルとして、簡単な GET メソッドおよび POST メソッドの API 設計書を作成します。
依存関係の追加
pom.xmlに下記を追加します:
<!-- https://mvnrepository.com/artifact/org.springframework.restdocs/spring-restdocs-mockmvc -->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<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>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>
API 実装
- コントローラインターフェース
package nob.example.easyapp.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import nob.example.easyapp.controller.model.LoginRequest;
import nob.example.easyapp.controller.model.LoginResponse;
import nob.example.easyapp.controller.model.MeRequest;
import nob.example.easyapp.controller.model.MeResponse;
/**
* 認証コントローラーのインターフェースです。
*
* @author nob
*/
@RestController
@RequestMapping(value = "/api/v1")
public interface AuthController {
/**
* 認証処理を呼び出します。
*
* @param request 認証リクエスト
* @return 認証結果
*/
@PostMapping(value = "/login")
LoginResponse login(@RequestBody LoginRequest request);
/**
* ユーザ情報取得処理を呼び出します。
*
* @param request ユーザ情報取得リクエスト
* @return ユーザ情報
*/
@GetMapping(value = "/me")
MeResponse me(MeRequest request);
}
- コントローラ実装
package nob.example.easyapp.controller.impl;
import org.springframework.web.bind.annotation.RestController;
import lombok.NonNull;
import nob.example.easyapp.controller.AuthController;
import nob.example.easyapp.controller.model.LoginRequest;
import nob.example.easyapp.controller.model.LoginResponse;
import nob.example.easyapp.controller.model.MeRequest;
import nob.example.easyapp.controller.model.MeResponse;
import nob.example.easyapp.service.AuthService;
import nob.example.easyapp.service.model.LoginInModel;
import nob.example.easyapp.service.model.MeInModel;
import nob.example.easyapp.service.model.MeOutModel;
/**
* AuthControllerの実装クラスです。
*
* @author nob
*/
@RestController
public class AuthControllerImpl implements AuthController {
@NonNull
private AuthService authService;
public AuthControllerImpl(AuthService authService) {
this.authService = authService;
}
@Override
public LoginResponse login(LoginRequest request) {
return new LoginResponse(
authService.login(new LoginInModel(request.getName(), request.getPassword())).isValid());
}
@Override
public MeResponse me(MeRequest request) {
MeOutModel meOutModel = authService.me(new MeInModel(request.getName()));
return new MeResponse(meOutModel.getName(), meOutModel.getAge());
}
}
- コントローラテスト
package nob.example.easyapp.controller;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import nob.example.easyapp.controller.model.LoginRequest;
import nob.example.easyapp.controller.model.MeRequest;
import nob.example.easyapp.service.AuthService;
import nob.example.easyapp.service.model.LoginInModel;
import nob.example.easyapp.service.model.LoginOutModel;
import nob.example.easyapp.service.model.MeInModel;
import nob.example.easyapp.service.model.MeOutModel;
/**
* AuthControllerImplのテストクラスです。
*
* @author nob
*/
@WebMvcTest
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class AuthControllerImplTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockitoBean
private AuthService authService;
/**
* loginのテスト 正常系
*/
@Test
void test_login_success() {
// リクエストの作成
LoginRequest request = new LoginRequest("nob", "passwd");
// serviceのモック化
Mockito.when(authService.login(new LoginInModel(request.getName(), request.getPassword())))
.thenReturn(new LoginOutModel(true));
try {
mockMvc.perform(post("/api/v1/login")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON))
.andDo(document(
"asciidoc/api/v1/login",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(
fieldWithPath("name").description("ユーザ名"),
fieldWithPath("password").description("パスワード")),
responseFields(
fieldWithPath("valid").description("認証可否"))));
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
/**
* meのテスト 正常系
*/
@Test
void test_me_success() {
// リクエストの作成
MeRequest request = new MeRequest("nob");
// serviceのモック化
Mockito.when(authService.me(new MeInModel(request.getName()))).thenReturn(new MeOutModel("nob", 13));
try {
mockMvc.perform(get("/api/v1/me")
.queryParam("name", "nob")
.contentType(MediaType.APPLICATION_JSON))
.andDo(document("asciidoc/api/v1/me",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
queryParameters(
parameterWithName("name").description("ユーザ名")),
responseFields(
fieldWithPath("name").description("ユーザ名"),
fieldWithPath("age").description("年齢"))));
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}
asciidoc
src/main/asciidoc配下に下記index.adocを追加します:
:toc: left
:source-highlighter: highlightjs
= サンプルAPI仕様書
サンプルAPI群の仕様書です。
:base_path: ../../../target/snippets/asciidoc
== Auth
認証APIです。
:auth: {base_path}/api/v1
=== Login
認証処理を行います。
:login: {auth}/login
.request field
include::{login}/request-fields.adoc[]
.response field
include::{login}/response-fields.adoc[]
.example request
include::{login}/curl-request.adoc[]
.example response
include::{login}/response-body.adoc[]
=== Me
ユーザ情報を取得します。
:me: {auth}/me
.query parameter
include::{me}/query-parameters.adoc[]
.response field
include::{me}/response-fields.adoc[]
.example request
include::{me}/curl-request.adoc[]
.example response
include::{me}/response-body.adoc[]
:xxx:で適宜変数を定め、include::で生成したスニペットを読み込んでいます。
設計書生成手順
- 単体テストを実行します。テストに成功すると、
target/snippets配下に、document内で定めたパスに各種スニペットが出力されます。
./mvnw test
- パッケージをビルドします。
target/generated-docs配下に設計書本体index.htmlが出力されます。
./mvnw package