Anyone can write code. A salty developer will read that sentence and think "Yeah, anyone can break code too!" Luckily, unit tests can help prevent code breakage. Unit tests protect feature requirements from breaking as new features get added. Unit tests can also be useful to protect an API endpoint from change.
Today, we will look at an API endpoint written using Spring MVC and how to unit test it with Spring Test's Mock MVC and Mockito.
Given an Entity Object below:
public class widget{
private Long id;
private String description;
// getters & setters
}
And it's respective DTO
public class WidgetRestApiModel {
private Long id;
private String description;
public static WidgetRestApiModel fromWidget(Widget widget) {
WidgetRestApiModel widgetRestModel = new WidgetRestApiModel();
widgetRestModel.setId(widget.getId());
widgetRestModel.setDescription(widget.getDescription());
return widgetRestApiModel;
}
// getters and setters
}
Using Spring MVC we can define the API endpoints to create and get a Widget:
-
POST http://hostname/widgets
-
GET http://hostname/widgets/{widgetId}
I assume the reader is already familiar with Spring MVC. If this is not the case is it simple to infer the purpose of each method.
@RestContoller
public class WidgetController {
@Autowired
private WidgetService widgetService;
/**
* Create a new widget
*
* @param widgetCreationModel the model to create a widget
* @return the newly created widget with an id
*/
@RequestMapping(method = RequestMethod.POST, value = "widgets")
public ResponseEntity postWidgets(@Valid @RequestBody WidgetCreationModel widgetCreationModel) {
Widget widget = widgetService.createwidget(widgetCreationModel);
return new ResponseEntity<>(widgetRestApiModel.fromwidget(widget), HttpStatus.OK);
}
/**
* Get a widget by Id
*
* @param widgetId Long the widget id to get
* @return a widget
*/
@RequestMapping(method = RequestMethod.GET, value = "widgets/{widgetId}")
public ResponseEntity getWidget(@PathVariable("widgetId") Long widgetId) {
return new ResponseEntity<>(widgetRestApiModel.fromwidget(widgetService.findwidgetById(widgetId)), HttpStatus.OK);
}
}
The unit test will test the the GET widget endpoint
Unit test for GET
/**
* Test GET widget
*/
@Test
public void getWidget() throws Exception {
Widget widget = TestUtils.generateWidget(3L);
Mockito.when(widgetServiceMock.findWidgetById(widget.getId())).thenReturn(widget);
ResultActions resultActions = mockMvc.perform(get("/widgets/" + widget.getId()))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtils.APPLICATION_JSON_UTF8));
WidgetRestApiModel widgetRestApiModel = mapper.readValue(resultActions.andReturn().getResponse().getContentAsString(), widgetRestApiModel.class);
Assert.assertEquals(widget.getId(), widgetRestApiModel.getId());
Assert.assertEquals(widget.getDescription(), widgetRestApiModel.getDescription());
verify(widgetServiceMock, times(1)).findwidgetById(widget.getId());
}
Dissecting the above unit test. You see that a Widget is generated using a TestUtils class. The TestUtils class is a simple utility class for unit testing that constructs needed objects with static methods. In this case, the generateWidget class creates a Widget with an id of 3. This Widget instance will be used throughout the unit test to assert proper behavior.
public class TestUtils {
...
public static Widget generateWidget(Long id) {
Widget widget = new Widget();
widget.setId(id);
widget.setDescription("widget description " + id);
return widget;
}
}
A TestUtils class like this is great when unit testing applications. It allows the developer to create objects in deterministic way and saves enormous amount of effort through simple code reuse.
Next, the call to the WidgetService::findWidgetById
needs to be mocked. Mocking is a way of limiting the scope of the unit test. In the above, the WidgetService::findWidgetByid
method will be mocked, meaning it will be given a predetermined object to return instead of actually executing. As a result, the unit test will only be testing logic in the controller method and not the service method. Applying scope to a unit test gives the unit test greater precision. The mock framework being used is Mockito.
Mockito is a "Tasty mocking framework for unit tests in Java." (Their words not mine.) It allows developers to mock objects and in this case the WidgetService
. The line method tells Mockito to return the widget previously instantiated when the findWidgetById
method is called.
Finally, it is time to "start" the test. MockMvc will perform a GET request on the "/widgets" endpoint for the id of the widget created. MockMvc is told to expect an HTTP Status and expect the content type to be JSON. Once this line is executed the unit test will enter the method WidgetController::getWidget
.
Now, to test the result of the method, the unit test must convert the JSON object back to a WidgetRestApiModel
and Assert the values are as expected.
mapper
is an instance of a Jackson Object Mapper Jackson Object Mapper. It will convert the response from the controller method to a WidgetRestApiModel
. This object can then be used in standard JUnit assertions.
Finally, Mockito allows the unit test to verify the number of times each mocked method was called.
In Summation this unit test:
- verifies the existence of the API endpoint
/widgets/{widgetId}
- verifies that the correct HTTP status is returned along with the proper content type
- verifies the conversion from an entity object to a DTO rest object does not change the data
- verifies that the service layer was used properly
Writing a unit test for the POST method will be left as a exercise to the reader.
Happy Unit Testing, Charles