Spring REST Docs

Comme son nom l'indique, Spring REST Docs est un outil Spring d'aide à la documentation des API Restful. En s'articulant autour des jeux des tests Spring MVC, Spring REST Docs génère sans effort une documentation à jour et détaillée des API de l'application. Spring REST Docs se base sur l'utilisation de "snippets" (données de texte brut) générés suite à l’exécution de tests. La mise en forme est assurée par Asciidoctor (par défaut) qui va produire des pages HTML.

Avantages :

  • La documentation reste en cohérence avec l'implémentation du service REST ;
  • La mise en forme est homogène ;
  • Simple à mettre en place (configuration, package de la documentation) ;
  • Customisation possible (données remontées, templates asciidoc) ;
  • Support JSON et XML.

Pré-requis :

  • Java 7
  • Spring framework 4.3

Getting started

La démonstration suivante se base sur Spring MockMvc pour les tests. Spring REST Docs est aussi compatible avec REST Assured. Dans cet article, pour l'exemple, je suis parti d'un projet Spring Boot en version 1.5.7, soit :

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>1.5.7.RELEASE</version>
	<relativePath/>
</parent>

Ce projet dispose d'une API Rest simple et d'un test unitaire réalisé avec MockMVC.

Dépendances et plugins (maven)

La première étape consiste à ajouter les dépendances Spring REST Docs à votre pom.xml :

<dependency> 
	<groupId>org.springframework.restdocs</groupId>
	<artifactId>spring-restdocs-mockmvc</artifactId>
	<scope>test</scope>
</dependency>
pom.xml

Ensuite, il est nécessaire de configurer le plugin Asciidoctor, qui va exploiter les snippets générés durant la phase de tests.

<build>
	<plugins>
		<plugin>
			<groupId>org.asciidoctor</groupId>
			<artifactId>asciidoctor-maven-plugin</artifactId>
			<version>1.5.5</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>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>
pom.xml

Cela permet de remplir les templates (fichiers *.adoc) avec les valeurs remontés des tests. La propriété <snippets> pointe par défaut sur "target/generated-snippets".

La suite est optionnelle, mais permet de packager la documentation à l’intérieur de l'archive jar. Cela peut servir pour exposer directement la documentation en ressource statiques.

<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>
					${project.build.outputDirectory}/static/docs
				</outputDirectory>
				<resources>
					<resource>
						<directory>
							${project.build.directory}/generated-docs
						</directory>
					</resource>
				</resources>
			</configuration>
		</execution>
	</executions>
</plugin>
pom.xml

PS : L'équivalence Gradle est disponible sur la page de documentation Spring.

Java

On part du principe qu'il existe une classe de tests, pour laquelle on souhaite ajouter la génération de la documentation à l'aide de Spring REST Docs. Soit :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {
        Application.class
})
public class UserRestTest {

    protected MockMvc mockMvc;

    @Autowired private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .build();
    }

    @Test
    public void getUser() throws Exception {
        mockMvc.perform(RestDocumentationRequestBuilders.get("/users/1"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
    }
}
UserRestTest.java

Pour configurer la génération des snippets, on ajoute une règle JUnit à notre classe de test :

@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
UserRestTest.java

Le dossier où sont générés les snippets est configurable. Par défaut, il pointe sur "target/generated-snippets".

Et ajouter au MockMvc :

mockMvc = MockMvcBuilders
             .webAppContextSetup(webApplicationContext)
             .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation))
             .build();
UserRestTest.java

Tous les tests de la classe, qui font appel à mockMvc, appliqueront cette règle. Il est possible d'étendre cette configuration à une classe générique pour être appliquée à une série de classes de tests.

Pour finir la partie Java, il reste une étape : modifier le test pour qu'il génère ses snippets :

mockMvc.perform(RestDocumentationRequestBuilders.get("/users/1"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcRestDocumentation.document("test"))
                .andReturn();
UserRestTest.java

La méthode statique "document" va écrire le résultat (snippets) dans le dossier de destination renseigné en paramètre.

(Optionnel) On peut aussi faire cette déclaration globalement, notamment pour éviter de modifier les N tests de notre application :

mockMvc = MockMvcBuilders
             .webAppContextSetup(webApplicationContext)
               .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(document("{class-name}/{method-name}"))
                .build();
UserRestTest.java

On peut utiliser les paramètres suivants pour configurer le dossier de destination des snippets :

  • {class-name}, nom de la classe ;
  • {method-name}, nom de la méthode ;
  • {step}, nombre de fois où le service a été appelé.

A partir de là, il est possible d'ajuster la configuration ou le détail de la documentation pour Allez plus loin...

Templates Asciidoctor

Pour l'exemple, on va utiliser Asciidoctor pour produire le code HTML qui sera notre documentation finale. C'est le moteur de template par défaut de Spring REST Docs.

Le plugin maven Asciidoctor, configuré en début de projet, va chercher le template dans le répertoire : src/main/asciidoc

Voici un exemple de template pour les tests codés ci-dessus :

= DEMO DOCS
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:

[[resources-user]]
== Utilisateur

[[resource-get-user]]
=== GET
Requête `GET` récupère le détail d'un utilisateur authentifié.

==== Requête HTTP

include::{snippets}/get-user/http-request.adoc[]

==== Example Request

include::{snippets}/get-user/curl-request.adoc[]

==== Example Response

include::{snippets}/get-user/http-response.adoc[]
user.adoc

{snippets} est le paramètre configuré préalablement, pointant sur le chemin vers les snippets.

Pour plus d'information sur Asciidoctor : http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/

Premier test

On exécute les tests à l'aide de Maven. La documentation générée est accessible sous "target/generated-docs/index.html"

Dans le template utilisé, la page dispose d'un menu, pour naviguer dans les API de l'application.

Allez plus loin...

Snippets

Il est possible d'ajuster la configuration de l'outil, notamment des "snippets" générés pour plus de détails sur l'API. Reprenons la configuration réalisée pour le test d'exemple, et ajoutons des snippets :

mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
                        .snippets()
                        .withDefaults(
                                CliDocumentation.curlRequest(),
                                HttpDocumentation.httpRequest(),
                                HttpDocumentation.httpResponse(),
                                PayloadDocumentation.responseFields(
                                        PayloadDocumentation.fieldWithPath("id").description("Identifiant"),
                                        PayloadDocumentation.fieldWithPath("firstName").description("Prénom"),
                                        PayloadDocumentation.fieldWithPath("lastName").description("Nom"),
                                        PayloadDocumentation.fieldWithPath("birthday").description("Date de naissance"),
                                        PayloadDocumentation.fieldWithPath("locked").description("Compte vérouillé")
                                )
                        ))
                .build();

Cette configuration permet de définir les données par défaut que l'on va générer pour chaque API. En cas d'absence de données pour ce type de champs (ex : paramètre de requête), le snippet sera tout de même créé en indiquant l'absence de données, par exemple : "No parameters".

On remarque que certaines données vont nécessiter d'être décrites en précision pour pouvoir être utilisées dans la documentation. C'est le cas de "responseFields" qui décrit chaque champ de la réponse JSON. Nous verrons par la suite qu'il est possible d'automatiser cette étape en se basant sur la Javadocs du projet.

Voici la liste des snippets disponibles dans les librairies rest docs :

  • curlRequest (actif par défaut)
  • httpieRequest (actif par défaut)
  • httpRequest (actif par défaut)
  • httpResponse (actif par défaut)
  • requestBody (actif par défaut)
  • reponseBody (actif par défaut)
  • requestHeader
  • responseHeader
  • description
  • methodAndPath
  • section
  • authorization
  • requestFields
  • responseFields
  • requestParameters
  • pathParameters

Javadocs

Pour aller plus loin dans la génération de la documentation, il est possible de réutiliser la documentation du code (javadocs) pour enrichir la document des API. Pour cela il est nécessaire de mettre en place des outils qui vont remplacer les snippets de Spring.

1 - Ajout de la librairie (maven)

Cette librairie embarque des snippets qui vont intégrer les données JSON générées par les plugins décrit ci-dessous.

<dependency>
	<groupId>capital.scalable</groupId>
	<artifactId>spring-auto-restdocs-core</artifactId>
</dependency>

2 - Configuration des plugins

<plugin>
	<artifactId>maven-javadoc-plugin</artifactId>
	<extensions>true</extensions>
	<executions>
		<execution>
			<id>generate-javadoc-json</id>
			<phase>compile</phase>
			
			<goals>
				<goal>javadoc-no-fork</goal>
			</goals>
			
			<configuration>
				<doclet>capital.scalable.restdocs.jsondoclet.ExtractDocumentationAsJsonDoclet</doclet>
				
				<docletArtifact>
					<groupId>capital.scalable</groupId>
					<artifactId>spring-auto-restdocs-json-doclet</artifactId>
					<version>1.0.6</version>
				</docletArtifact>
				
				<destDir>generated-javadoc-json</destDir>
				<reportOutputDirectory>${project.build.directory}</reportOutputDirectory>
				<useStandardDocletOptions>false</useStandardDocletOptions>
				<show>package</show>
				
			</configuration>
		</execution>
	</executions>
</plugin>

Cette configuration de plugin permet de formaliser la javadoc au format JSON.

3 - Modification dans le code

Ensuite, il faut enrichir la configuration du "mockMvc" précédemment déclaré. Pour cela, généralisons la configuration Rest Docs dans la méthode "setUp" du test unitaire :

@Autowired 
protected ObjectMapper objectMapper;

@Before
public void setUp() {
	mockMvc = MockMvcBuilders
		.webAppContextSetup(webApplicationContext)
		.alwaysDo(prepareJackson(objectMapper))
		.alwaysDo(document("{method-name}",
					preprocessRequest(),
					preprocessResponse()))
		.apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
			   .snippets()
			   .withDefaults(
					CliDocumentation.curlRequest(),
					HttpDocumentation.httpRequest(),
					HttpDocumentation.httpResponse(),
					AutoDocumentation.responseFields()
				))
		.build();
}

On ajoute un traitement des données de requête et de réponse. Ne pas oublier de injecter un ObjectMapper Jackson pour traiter les données Javadocs en JSON. Et finalement, on replace le snippet responseField de Spring par celui de la librairie que l'on a ajouté à nos dépendances Maven :

import org.springframework.restdocs.http.HttpDocumentation;
--> import capital.scalable.restdocs.AutoDocumentation;

Voici les snippets mis à disposition par la librairie :

  • description
  • methodAndPath
  • section
  • requestFields
  • responseFields
  • requestParameters
  • pathParameters

Voici le résultat :

On remarque, en plus des commentaires de la javadocs, que les annotations de validation sont aussi interprétées. Notamment pour l'identifiant qui est @NotNull, et pour les champs String qui sont limités à 50 caractères.

Et...

Pour pousser son utilisation encore plus loin, il est possible d'ajouter un "Listener" JUnit qui va générer à la volée le template Asciidoctor. Mais on sort du périmètre de Spring Rest Docs. Le github du projet utilisé pour illustrer cet article embarque cette fonctionnalité.

Conclusion

En résumé, Spring Rest Docs s'intègre facilement à un projet existant. Au premier abord, il ne nécessite pas un gros travail de configuration mais offre tout de même des possibilités de customisation intéressante.

 

Code source : https://github.com/Helheim35/demodocs

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.