Spring Boot SpringDoc based example
@SpringBootApplication
class SpringBootOpenapiWithTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootOpenmapiWithTestApplication.class, args);
}
/**
* Bean to initialize the resources.
*/
@Bean
public ExampleResourceReaderBean exampleResourceReaderBean() {
ExampleResourceReaderBean exampleResourceReaderBean = new ExampleResourceReaderBean();
exampleResourceReaderBean.initializeResources();
return exampleResourceReaderBean;
}
/**
* Operation customizer.
*/
@Bean
public ApiResponseAndExampleCustomizer customizer() {
return new ApiResponseAndExampleCustomizer();
}
/**
* OpenAPI object customizer bean.
*/
@Bean
public OpenApiCustomiser openApiCustomiser() {
return new OpenApiExampleExtenderCustomizer();
}
}
@RestController
class UserController {
@GetMapping(path = "/user", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity getUser(@RequestParam(name = "id", required = false) String id) {
if ("BAD".equals(id)) return ResponseEntity.badRequest().body(new ErrorResponse("Bad " + id, "Cause it went bad"));
else if ("BAD2".equals(id)) return ResponseEntity.internalServerError().body(new ErrorResponse("Internal Server Error " + id, "Bad because internal"));
else return ResponseEntity.ok(new UserResponse("joe", "Joe Big"));
}
@PostMapping(path = "/user", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity postUser(@RequestBody UserRequest userRequest) {
if (!userRequest.getPassword().equals(userRequest.getPasswordConfirmation())) {
return ResponseEntity.unprocessableEntity().body(new ErrorResponse("Passwords must match", "Cause it went bad"));
}
if (userRequest.getUsername().equals("bob")) {
return ResponseEntity.unprocessableEntity().body(new ErrorResponse("Username already exists", "Cause it went bad"));
}
return ResponseEntity.status(HttpStatus.CREATED).body(new UserResponse("joe", "Joe Big"));
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class UserRequest {
private String username;
private String password;
private String passwordConfirmation;
private String fullName;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class ErrorResponse {
private String message;
private String cause;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class UserResponse {
private String username;
private String fullName;
}
}
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void getUser_isOk() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(status().isOk())
.andDo(result -> new ApiResponseDocumentReporter("getUser", "Standard response").handle(result));
}
@Test
void getUser_isBadRequest() throws Exception {
mockMvc.perform(get("/user?id=BAD"))
.andExpect(status().isBadRequest())
.andDo(result -> new ApiResponseDocumentReporter("getUser", "When shit happens").handle(result));
}
@Test
void getUser_isInternalError_1() throws Exception {
mockMvc.perform(get("/user?id=BAD2"))
.andExpect(status().isInternalServerError())
.andDo(result -> new ApiResponseDocumentReporter("getUser", "When coupon code does not exist").handle(result));
}
@Test
void getUser_isInternalError_2() throws Exception {
mockMvc.perform(get("/user?id=BAD2"))
.andExpect(status().isInternalServerError())
.andDo(result -> new ApiResponseDocumentReporter("getUser", "When shit explodes").handle(result));
}
@Test
void postUser_WhenPasswordDoesNotMatch() throws Exception {
UserController.UserRequest userRequest = new UserController.UserRequest("alex123", "password123", "password12", "Alex King");
mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).content(asJsonString(userRequest)))
.andExpect(status().isUnprocessableEntity())
.andDo(result -> new ApiResponseDocumentReporter("postUser", "When passwords does not match").handle(result))
.andDo(result -> new RequestBodyDocumentReporter("postUser", "Will throw error").handle(result));
}
@Test
void postUser_WhenUsernameAlreadyExist() throws Exception {
UserController.UserRequest userRequest = new UserController.UserRequest("bob", "password123", "password123", "Bob Sug");
mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(asJsonString(userRequest)))
.andExpect(status().isUnprocessableEntity())
.andDo(result -> new ApiResponseDocumentReporter("postUser", "When username already exist").handle(result))
.andDo(result -> new RequestBodyDocumentReporter("postUser", "Will throw error because user already exist").handle(result));
}
@Test
void postUser_WhenEverythingIsOk() throws Exception {
UserController.UserRequest userRequest = new UserController.UserRequest("new-bob", "password123", "password123", "Bob Sug");
mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(asJsonString(userRequest)))
.andExpect(status().isCreated())
.andDo(result -> new ApiResponseDocumentReporter("postUser", "Successful operation").handle(result))
.andDo(result -> new RequestBodyDocumentReporter("postUser", "Creates a user").handle(result));
}
@Test
void postUser_WhenEverythingIsOkXml() throws Exception {
UserController.UserRequest userRequest = new UserController.UserRequest("new-bob", "password123", "password123", "Bob Sug");
mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_XML).content(asJsonString(userRequest)))
.andExpect(status().isCreated())
.andDo(result -> new ApiResponseDocumentReporter("postUser", "Successful operation").handle(result))
.andDo(result -> new RequestBodyDocumentReporter("postUser", "Creates a user").handle(result));
}
static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
By default, the generated files are going to be created to the following
paths: - target/classes/openapi-extender/requests/
-
target/classes/openapi-extender/responses/
requests/postUser_201_Creates a user.json:
{
"username": "new-bob",
"password": "password123",
"passwordConfirmation": "password123",
"fullName": "Bob Sug"
}
responses/postUser_201_Successful operation.json:
{
"username" : "joe",
"fullName" : "Joe Big"
}
responses/postUser_422_When passwords does not match.json:
{
"message" : "Passwords must match",
"cause" : "Cause it went bad"
}
Swagger UI Preview
Generated OpenAPI documentation
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8080
description: Generated server url
paths:
/user:
get:
tags:
- user-controller
operationId: getUser
parameters:
- name: id
in: query
required: false
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: string
examples:
Standard response:
description: ""
$ref: '#/components/examples/response_getUser_application_json_Standard
response'
application/xml:
schema:
type: string
examples:
Standard response:
description: ""
$ref: '#/components/examples/response_getUser_application_json_Standard
response'
"400":
content:
application/json:
examples:
When bad thing happens:
description: Happens because the id is BAD and it is code to fail
in these cases
$ref: '#/components/examples/response_getUser_application_json_When
bad thing happens'
"500":
content:
application/json:
examples:
When coupon code does not exist:
description: ""
$ref: '#/components/examples/response_getUser_application_json_When
coupon code does not exist'
When server explodes:
description: ""
$ref: '#/components/examples/response_getUser_application_json_When
server explodes'
post:
tags:
- user-controller
operationId: postUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserRequest'
examples:
Creates a user:
description: "Returns: HTTP 201"
$ref: '#/components/examples/request_postUser_application_json_Creates
a user'
Will throw error because user already exist:
description: "Returns: HTTP 422"
$ref: '#/components/examples/request_postUser_application_json_Will
throw error because user already exist'
Will throw error:
description: "Returns: HTTP 422"
$ref: '#/components/examples/request_postUser_application_json_Will
throw error'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: string
application/xml:
schema:
type: string
"201":
content:
application/json:
examples:
Successful operation:
description: ""
$ref: '#/components/examples/response_postUser_application_json_Successful
operation'
application/xml:
examples:
Successful operation:
description: ""
$ref: '#/components/examples/response_postUser_application_xml_Successful
operation'
"422":
content:
application/json:
examples:
When passwords does not match:
description: ""
$ref: '#/components/examples/response_postUser_application_json_When
passwords does not match'
When username already exist:
description: ""
$ref: '#/components/examples/response_postUser_application_json_When
username already exist'
components:
schemas:
UserRequest:
type: object
properties:
username:
type: string
password:
type: string
passwordConfirmation:
type: string
fullName:
type: string
examples:
request_postUser_application_json_Creates a user:
description: ""
value: |-
{
"username" : "new-bob",
"password" : "password123",
"passwordConfirmation" : "password123",
"fullName" : "Bob Sug"
}
response_postUser_application_json_Successful operation:
description: ""
value: |-
{
"username" : "joe",
"fullName" : "Joe Big"
}
response_getUser_application_json_When coupon code does not exist:
description: ""
value: |-
{
"message" : "Internal Server Error BAD2",
"cause" : "Bad because internal"
}
response_getUser_application_json_When server explodes:
description: ""
value: |-
{
"message" : "Internal Server Error BAD2",
"cause" : "Bad because internal"
}
response_getUser_application_json_When bad thing happens:
description: Happens because the id is BAD and it is code to fail in these cases
value: |-
{
"message" : "Bad BAD",
"cause" : "Cause it went bad"
}
request_postUser_application_json_Will throw error:
description: ""
value: |-
{
"username" : "alex123",
"password" : "password123",
"passwordConfirmation" : "password12",
"fullName" : "Alex King"
}
response_postUser_application_xml_Successful operation:
description: ""
value: |
<LinkedHashMap>
<username>joe</username>
<fullName>Joe Big</fullName>
</LinkedHashMap>
response_postUser_application_json_When username already exist:
description: ""
value: |-
{
"message" : "Username already exists",
"cause" : "Cause it went bad"
}
request_postUser_application_json_Will throw error because user already exist:
description: ""
value: |-
{
"username" : "bob",
"password" : "password123",
"passwordConfirmation" : "password123",
"fullName" : "Bob Sug"
}
response_postUser_application_json_When passwords does not match:
description: ""
value: |-
{
"message" : "Passwords must match",
"cause" : "Cause it went bad"
}
response_getUser_application_json_Standard response:
description: ""
value: |-
{
"username" : "joe",
"fullName" : "Joe Big"
}