Simple API Documentation with Spring Auto REST Docs

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 document( entityName + "/{method-name}", .... 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 mockMvc.perform 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.

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.