The problem

A couple of days ago I was working on a project of one of our customers. One of their new applications needed to expose a public API, and of course we needed to hand over a set of documentation about those REST endpoints. Some people were already starting to do this manually in Confluence, but after a while (and we’re talking about a timespan just under 2 hours) this became a tedious job. We had to continuously adjust the input & output contracts, the different endpoints,… Using Spring REST Docs I wanted to automatically document all of the public API endpoints, while we were also testing all of the components in the whole application. For some undisclosed reasons we simply couldn’t write integration tests, so we were stuck with our unit tests and mocked objects.

The solution

Imagine you have following service and controller in a simple Spring Boot application:

    @Service
    public class DeviceService {

        public List<Device> getDevices() {
        List<Device> devices = new ArrayList<>();

            /*
                Some business logic here...
            */

            return devices;
        }
    }

    @RestController
    @RequestMapping("devices")
    public class DeviceController {

        private DeviceService deviceService;

        @Autowired
        public DeviceController(DeviceService deviceService) {
            this.deviceService = deviceService;
        }

        @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
        public List<Device> getDevices() {
            return this.deviceService.getDevices();
        }
    }
    


Since this is a Spring Boot application both classes will automagically be instantiated. Because you need to annotate your unit tests at class level with @WebAppConfiguration and @SpringApplicationConfiguration, we can easily create a new Spring Boot application and use this for our documentation. In this new application we set the base package that needs to be scanned to our controller sub package, and create a mock implementation of our DeviceService.

    @SpringBootApplication(scanBasePackages = { "be.ordina.blog.controller" } )
    public class Application {

        @Bean
        public DeviceService getDeviceService() {
            return EasyMock.createStrictMock(DeviceService.class);
        }
    }
    


Our DeviceControllerTests class will then look something like this:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = { Application.class })
    @WebAppConfiguration
    public class DeviceControllerTests {

        @Rule
        public RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");

        @Autowired
        private WebApplicationContext context;

        @Autowired
        public DeviceService deviceService;

        private MockMvc mockMvc;

        @Before
        public void setUp() {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                                            .apply(documentationConfiguration(this.restDocumentation))
                                            .build();
        }

        @After
        public void cleanup() {
            EasyMock.verify(deviceService);
            EasyMock.reset(deviceService);
        }

        @Test
        public void getDevices() throws Exception {
            Device firstDevice = new Device("iPhone 6");
            Device secondDevice = new Device("Nexus 5");
            
            List<Device> devices = new ArrayList<>();
            devices.add(firstDevice);
            devices.add(secondDevice);

            EasyMock.expect(deviceService.getDevices()).andReturn(devices);

            EasyMock.replay(deviceService);

            this.mockMvc.perform(get("/devices").accept(MediaType.APPLICATION_JSON))
                        .andExpect(status().isOk())
                        .andExpect(jsonPath("$", hasSize(2)))
                        .andExpect(jsonPath("$[0].name", is(firstDevice.getName())))
                        .andExpect(jsonPath("$[1].name", is(secondDevice.getName())))
                        .andDo(document("device"));
        }
    }
    


So this is how I managed to get rid of the manual, tedious work and keep my unit tests - and got back to the more serious part of my life: coding like a monkey. =)

PS: All of the code above is checked in at our public github repo, so you are free to clone the working application! You can find it here!

Tim is a Principal Consultant at Ordina, where he helps fellow developers discovering top-notch technologies and methodologies as a Competence Leader for API & Microservices. You can also find him working on various parts of the Spring framework or helping out at NG-BE. When you can't get a hold of him, he will probably be under water since he is also an underwater photographer & rescue diver.