Integrate Asciidoc with Spring Restdocs
Preconditon
The following is based on Webflux
1
2
3
| asciidoctor version: 3.2.0
spring restdocs version: 2.0.4.RELEASE
dependency management: gradle
|
Some asciidoctor knowledge:
sourceDir: src/docs/asciidoc
outputDir: ${buildDir}/docs/asciidoc
Configuration
common config
The following config is not a complete configuration.
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
| // plugins
plugins {
id 'org.springframework.boot' version '2.3.0.M4'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
/* for rest docs */
id 'org.asciidoctor.jvm.convert' version '3.2.0'
}
configurations {
asciidoctorExtensions
}
ext {
set('snippetsDir', file('build/generated-snippets'))
set('springRestdocsVersion', '2.0.4.RELEASE')
}
dependencies {
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
/* for rest docs */
testImplementation "org.springframework.restdocs:spring-restdocs-webtestclient:${springRestdocsVersion}"
asciidoctorExtensions "org.springframework.restdocs:spring-restdocs-asciidoctor:${springRestdocsVersion}"
}
test {
outputs.dir snippetsDir
useJUnitPlatform()
}
asciidoctor {
configurations 'asciidoctorExtensions'
dependsOn test
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
}
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}") {
into 'static/docs'
}
}
|
create docs/asciidoc/ directory
like this
index.adoc & updateUser.adoc(e.g.)
index.adoc
1
2
3
4
5
6
7
8
9
10
11
12
13
| = Blog Restful API
Purple Mystic;
:toc: left
:toc-title: Chapter
:doctype: book
:icons: font
:source-highlighter: highlightjs
:sourcedir: {sourcedir}/user
include::createUser.adoc[]
include::updateUser.adoc[]
include::findUserById.adoc[]
include::findAllUsers.adoc[]
|
updateUser.adoc
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
| == *Backend: updateUser:*
=== Curl request:
include::{snippets}/updateUser/curl-request.adoc[]
=== HTTP request:
include::{snippets}/updateUser/http-request.adoc[]
=== HTTP response:
include::{snippets}/updateUser/http-response.adoc[]
=== Request using HTTPie:
include::{snippets}/updateUser/httpie-request.adoc[]
=== Request body:
include::{snippets}/updateUser/request-body.adoc[]
=== Response body:
include::{snippets}/updateUser/response-body.adoc[]
|
Other solution
- remove
:sourcedir: {sourcedir}/user
from index.adoc - reconfigure asciidoctor task in build.gradle, like this
1
2
3
4
5
6
7
8
9
10
| asciidoctor {
configurations 'asciidoctorExtensions'
dependsOn test
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
sources {
include '**/index.adoc'
}
baseDirFollowsSourceFile()
}
|
Unit Tests
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
| package team.star.blog.controller;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import team.star.blog.pojo.User;
import team.star.blog.service.UserService;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration;
/**
* @author mystic
*/
@WebFluxTest(UserController.class)
@ExtendWith(RestDocumentationExtension.class)
public class UserControllerTest {
@Autowired
private ApplicationContext context;
private WebTestClient client;
@MockBean
private UserService userService;
@BeforeEach
void setUp(RestDocumentationContextProvider provider) {
client = WebTestClient.bindToApplicationContext(context)
.configureClient()
.filter(
documentationConfiguration(provider)
.operationPreprocessors()
.withRequestDefaults(prettyPrint())
.withResponseDefaults(prettyPrint())
)
.build();
User u1 = User.builder().id(1).name("Mystic").build();
User u2 = User.builder().id(2).name("Ran").build();
when(userService.findAll()).thenReturn(Flux.fromIterable(List.of(u1, u2)));
when(userService.findById(Mockito.anyInt())).thenReturn(Mono.just(u1));
when(userService.save(Mockito.any(User.class))).thenReturn(Mono.just(u2));
}
@Test
void findUserById() {
client.get().uri("/user/{id}", 1)
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.consumeWith(document("findUserById",
pathParameters(parameterWithName("id").description("User ID"))
));
}
@Test
void findAllUsers() {
client.get().uri("/user").exchange()
.expectStatus().isOk()
.expectBodyList(User.class)
.consumeWith(document("findAllUsers"));
}
@Test
void createUser() {
User u3 = User.builder().name("cc").build();
client.post().uri("/user")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(u3), User.class)
.exchange()
.expectStatus().isCreated()
.expectBody()
.jsonPath("$.id").isEqualTo(2)
.consumeWith(document("createUser"));
}
@Test
void updateUser() {
User u2 = User.builder().id(2).name("cc").build();
client.patch().uri("/user")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(u2), User.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.name").isEqualTo("Ran")
.consumeWith(document("updateUser"));
}
}
|
Source Code
https://github.com/PurpleMystic-star/blog-backend