Micronaut Mastery: Documenting Our API Using Spring REST Docs
Spring REST Docs is a project to document a RESTful API using tests. The tests are used to invoke real REST calls on the application and to generate Asciidoctor markup snippets. We can use the generated snippets in an Asciidoctor document with documentation about our API. We can use Spring REST Docs to document a REST API we create using Micronaut.
First we must change our build file and include the Asciidoctor plugin and add dependencies to Spring REST Docs.
The following example Gradle build file adds the Gradle Asciidoctor plugin, Spring REST Docs dependencies and configures the test
and asciidoctor
tasks.
Spring REST Docs supports three different web clients to invoke the REST API of our application: Spring MockMVC, Spring Webflux WebTestClient and REST Assured.
We use REST Assured 3, because it has little dependencies on other frameworks (like Spring).
...
plugins {
id "org.asciidoctor.convert" version "1.5.8.1"
}
...
ext {
snippetsDir = file('build/generated-snippets')
springRestDocsVersion = '2.0.2.RELEASE'
}
dependencies {
asciidoctor "org.springframework.restdocs:spring-restdocs-asciidoctor:$springRestDocsVersion"
testCompile "org.springframework.restdocs:spring-restdocs-restassured:$springRestDocsVersion"
}
test {
outputs.dir snippetsDir
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
}
Let’s add a controller to our application that has two methods to return one or more Conference
objects.
We want to document both REST API resource methods. First we look at the Conference
class that is used:
package mrhaki.micronaut;
public class Conference {
private final String name;
private final String location;
public Conference(final String name, final String location) {
this.name = name;
this.location = location;
}
public String getName() {
return name;
}
public String getLocation() {
return location;
}
}
Next we write the following controller to implement /conference
to return multiple conferences and /conference/{name}
to return a specific conference.
The controller is dependent on the class ConferenceService
that contains the real logic to get the data, but the implementation is not important for our example to document the controller:
package mrhaki.micronaut;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Controller("/conference")
public class ConferenceController {
private final ConferenceService conferenceService;
public ConferenceController(final ConferenceService conferenceService) {
this.conferenceService = conferenceService;
}
@Get("/")
public Flux all() {
return conferenceService.all();
}
@Get("/{name}")
public Mono findByName(final String name) {
return conferenceService.findByName(name);
}
}
Now it is time to write our test that will invoke our controller and generate Asciidoctor markup snippets. We use Spock for writing the test in our example:
package mrhaki.micronaut
import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpStatus
import io.micronaut.runtime.server.EmbeddedServer
import io.restassured.builder.RequestSpecBuilder
import io.restassured.specification.RequestSpecification
import org.junit.Rule
import org.springframework.restdocs.JUnitRestDocumentation
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import static io.restassured.RestAssured.given
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration
class ConferenceApiSpec extends Specification {
@Shared
@AutoCleanup
private EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)
@Rule
private JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation()
private RequestSpecification spec
void setup() {
// Create a REST Assured request specification
// with some defaults. All URI's
// will not have localhost as server name,
// but api.example.com and the port is removed.
// All JSON responses are prettyfied.
this.spec = new RequestSpecBuilder()
.addFilter(
documentationConfiguration(restDocumentation)
.operationPreprocessors()
.withRequestDefaults(
modifyUris().host('api.example.com')
.removePort())
.withResponseDefaults(prettyPrint()))
.build()
}
void "get all conferences"() {
given:
final request =
given(this.spec)
// The server port is set and the value is
// used from embeddedServer.
.port(embeddedServer.URL.port)
.accept("application/json")
.filter(
document(
"all",
responseFields(
fieldWithPath("[].name").description("Name of conference."),
fieldWithPath("[].location").description("Location of conference.")
)))
when:
final response = request.get("/conference")
then:
response.statusCode() == HttpStatus.OK.code
}
void "get conference with given name"() {
given:
final request =
given(this.spec)
.port(embeddedServer.URL.port)
.accept("application/json")
.filter(
document(
"getByName",
responseFields(
fieldWithPath("name").description("Name of conference."),
fieldWithPath("location").description("Location of conference.")
)))
when:
final response = request.get("/conference/Gr8Conf EU")
then:
response.statusCode() == HttpStatus.OK.code
}
}
Finally we create a Asciidoctor document to describe our API and use the generated Asciidoctor markup snippets from Spring REST Docs in our document. We rely in our example document on the operation
macro that is part of Spring REST Docs to include some generated snippets:
= Conference API
== Get all conferences
operation::all[snippets="curl-request,httpie-request,response-body,response-fields"]
== Get conference using name
operation::getByName[snippets="curl-request,httpie-request,response-body,response-fields"]
We run the Gradle asciidoctor
task to create the documentation. When we open the generated HTML we see the following result:
Written with Micronaut 1.0.0.M4 and Spring REST Docs 2.0.2.RELEASE.