Contents
Maybe you have heard of Spring REST Docs? There is another possibility to simplify the API documentation process: Spring Auto REST Docs!
Testing for API documentation
The basic idea behind Spring REST Docs is to write test for the REST interface of your application. The tests could either be unit tests or integration tests. My philosophy is to write integration tests for the repository layer of your application. So you can be sure that access to a real database is possible. And every error in this layer is hard to find in tests of other layers.
In tests for RestController it is then possible to use mocking for every repository call and to focus on the functionality of the service. In my examples you find simple unit tests for REST interfaces which can be run in a few seconds.
Prepare the test
Example
Because the process of preparing a test class for testing with Spring Auto REST Docs is lengthy and always the same I use an abstract base class which contains all necessary fields and initializations:
public abstract class RestDocTest { @Rule public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( "target/generated-snippets" ); @Autowired private WebApplicationContext context; @Autowired ObjectMapper objectMapper; private String entityName; MockMvc mockMvc; public RestDocTest( String entityName ) { this.entityName = entityName; } @Before public void setUp() { SectionSnippet section = AutoDocumentation.sectionBuilder( ) .snippetNames( SnippetRegistry.PATH_PARAMETERS, SnippetRegistry.REQUEST_PARAMETERS, SnippetRegistry.REQUEST_FIELDS, SnippetRegistry.RESPONSE_FIELDS, SnippetRegistry.HTTP_REQUEST, SnippetRegistry.HTTP_RESPONSE ) .skipEmpty( true ) .build( ); this.mockMvc = MockMvcBuilders.webAppContextSetup( context ) // .addFilters(springSecurityFilterChain) .alwaysDo( prepareJackson( objectMapper ) ) .alwaysDo( document( entityName + "/{method-name}", preprocessRequest( ), preprocessResponse( replaceBinaryContent( ), limitJsonArrayLength( objectMapper ), prettyPrint( ) ) ) ) // .apply( MockMvcRestDocumentation.documentationConfiguration( restDocumentation ) .uris( ) .withScheme( "http" ) .withHost( "localhost" ) .withPort( 13001 ) .and( ) .snippets( ) .withDefaults( CliDocumentation.curlRequest( ), httpRequest( ), httpResponse( ), requestFields( ), responseFields( ), pathParameters( ), requestParameters( ), description( ), methodAndPath( ), section ) ) .build( ); } }
Snippets
The @Rule in lines 3 – 5 tells REST Docs to create code snippets from the code in the specified directory (target/generated-snippets).
Spring Auto REST Docs offers the possibility to create special snippets (section.adoc) for the API documentation which contains a survey of all relevant aspects of the REST call. This behavior is defined in lines 23 – 31.
Naming
How to display the requests and responses and where to put the snippets is described in lines 33 – 40. The directory for the snippets is defined by [java]document( entityName + “/{method-name}”, …[/java]. If the entityName is “person” and the test method name is “findAll” the directory name is “person/find-all”.
Some general information about http scheme, host, port and snippets are defined in lines 42 – 59.
The test class
An example test class that uses the RestDocTest base class could be:
@RunWith(SpringRunner.class) @WebMvcTest public class PersonControllerTest extends RestDocTest { private static final Person DONALD = new Person( 1, "Donald", "Duck", LocalDate.of( 1899, 3, 17 ) ); private static final Person DAGOBERT = new Person( 2, "Dagobert", "Duck", LocalDate.of( 1866, 12, 21 ) ); private static final Person DAISY = new Person( 3, "Daisy", "Duck", LocalDate.of( 1905, 7, 14 ) ); private static final Person DANIEL = new Person( 4, "Daniel", "Düsentrieb", LocalDate.of( 1900, 1, 1 ) ); public PersonControllerTest() { super( "person" ); } @MockBean private PeopleRepository repository; @Test public void findAllPeople() throws Exception { given( repository.findAll( ) ).willReturn( Arrays.asList( DONALD, DAGOBERT, DAISY, DANIEL ) ); mockMvc.perform( get( "/person" ).accept( APPLICATION_JSON_UTF8 ) ) .andExpect( status( ).isOk( ) ) .andExpect( jsonPath( "$", hasSize( 4 ) ) ); } @Test public void findOnePerson() throws Exception { given( repository.findOne( 1 ) ).willReturn( Optional.of( DONALD ) ); mockMvc.perform( get( "/person/{id}", 1 ).accept( APPLICATION_JSON_UTF8 ) ) .andExpect( status( ).isOk( ) ) .andExpect( jsonPath( "$.firstName", is( "Donald" ) ) ); } @Test public void findOnePersonWithWrongId() throws Exception { given( repository.findOne( 1 ) ).willReturn( Optional.empty( ) ); mockMvc.perform( get( "/person/{id}", 1 ).accept( APPLICATION_JSON_UTF8 ) ) .andExpect( status( ).isNotFound( ) ); } @Test public void createNewPerson() throws Exception { given( repository.maxId( ) ).willReturn( OptionalInt.of( 10 ) ); given( repository.save( Mockito.any( ) ) ).willReturn( DAGOBERT ); mockMvc.perform( post( "/person" ).accept( APPLICATION_JSON_UTF8 ) .contentType( APPLICATION_JSON_UTF8 ) .content( objectMapper.writeValueAsBytes( DAGOBERT ) ) ) .andExpect( status( ).isCreated( ) ) .andExpect( jsonPath( "$.id", is( 2 ) ) ); } @Test public void deletePerson() throws Exception { given( repository.exists( 1 ) ).willReturn( true ); mockMvc.perform( delete( "/person/{id}", 1 ) ).andExpect( status( ).isOk( ) ); } @Test public void deletePersonWithWrongId() throws Exception { given( repository.exists( 1 ) ).willReturn( false ); mockMvc.perform( delete( "/person/{id}", 1 ) ).andExpect( status( ).isNotFound( ) ); } }
Let’s pick the test method findOnePerson (e.g. lines 41- 48). We have to mock the repository call used int the controller method and then it is possible to use [java]mockMvc.perform[/java] to call a REST verb (GET) with some parameters. The test functionality andExpect checks the result of the request.
There is no additional code necessary to produce the API documentation snippets. When you use pure Spring REST Docs you have to declare several documentation parts for path parameters, request and response fields etc.