Unit Testing of Spring MVC Controllers: REST API

Spring MVC provides an easy way to create REST APIs. However, writing comprehensive and fast unit tests for these APIs has been troublesome. The release of the Spring MVC Test framework gave us the possibility to write unit tests which are readable, comprehensive and fast.

This blog post describes how we can write unit tests for a REST API by using the Spring MVC Test framework. During this blog post we will write unit tests for controller methods which provide CRUD functions for todo entries.

Let’s get started.

Getting The Required Dependencies with Maven

We can get the required testing dependencies by adding the following dependency declarations to our POM file:

Hamcrest 1.3 (hamcrest-all). We use Hamcrest matchers when we are writing assertions for the responses.

Junit 4.11. We need to exclude the hamcrest-core dependency because we already added the hamcrest-all dependency.

Mockito 1.9.5 (mockito-core). We use Mockito as our mocking library.

Spring Test 3.2.3.RELEASE

JsonPath 0.8.1 (json-path and json-path-assert). We use JsonPath when we are writing assertions for JSON documents returned by our REST API.

Let’s move on and talk a bit about the configuration of our unit tests.

Configuring Our Unit Tests

The unit tests which we will write during this blog post use the web application context based configuration. This means that we configure the Spring MVC infrastructure by using either an application context configuration class or a XML configuration file.

Because the first part of this tutorial described the principles which we should follow when we are configuring the application context of our application, this issue is not discussed in this blog post.

However, there is one thing that we have to address here.

The application context configuration class (or file) which configures the web layer of our example application does not create an exception resolver bean. The SimpleMappingExceptionResolver class used in the earlier parts of this tutorial maps exception class name to the view which is rendered when the configured exception is thrown.

This makes sense if we are implementing a “normal” Spring MVC application. However, if we are implementing a REST API, we want to transform exceptions into HTTP status codes. This behavior is provided by the ResponseStatusExceptionResolver class which is enabled by default.

Next we will see the Spring MVC Test framework in action and write unit tests for the following controller methods:

The first controller methods returns a list of todo entries.

The second controller method returns the information of a single todo entry.

The third controller method adds a new todo entry to the database and returns the added todo entry.

Get Todo Entries

The first controller method returns a list of todo entries which are found from the database. Let’s start by taking a look at the implementation of this method.

Expected Behavior

The controller method which returns all todo entries stored to the database is implemented by following these steps:

It processes GET requests send to url ‘/api/todo’.

It gets a list of Todo objects by calling the findAll() method of the TodoService interface. This method returns all todo entries which are stored to the database. These todo entries are always returned in the same order.

Our unit test uses a constant called APPLICATION_JSON_UTF8 which is declared in the TestUtil class. The value of that constant is a MediaType object which content type is ‘application/json’ and character set is ‘UTF-8′.

Get Todo Entry

The second controller method which we have to test returns the information of a single todo entry. Let’s find out how this controller method is implemented.

Expected Behavior

The controller method which returns the information of a single todo entry is implemented by following these steps:

It processes GET requests send to url ‘/api/todo/{id}’. The {id} is a path variable which contains the id of the requested todo entry.

It obtains the requested todo entry by calling the findById() method of the TodoService interface and passes the id of the requested todo entry as a method parameter. This method returns the found todo entry. If no todo entry is found, this method throws a TodoNotFoundException.

Our example application has an exception handler class which handles application specific exceptions thrown by our controller classes. This class has an exception handler method which is called when a TodoNotFoundException is thrown. The implementation of this method writes a new log message to the log file and ensures that the HTTP status code 404 is send back to the client.

It Adds a new todo entry to the database by calling the add() method of the TodoService interface and passes the TodoDTO object as a method parameter. This method adds a new todo entry to the database and returns the added todo entry.

Note: Spring MVC does not guarantee the ordering of the field errors. In other words, the field errors are returned in random order. We have to take this into account when we are writing unit tests for this controller method.

On the other hand, if the validation does not fail, our controller method returns the following JSON document to the client:

{
"id":1,
"description":"description",
"title":"todo"
}

We have to write two unit tests for this controller method:

We have to write a test which ensures that our application is working properly when the validation fails.

We have to write a test which ensures that our application is working properly when a new todo entry is added to the database.

Let’s find out how we can write these tests.

Test 1: Validation Fails

Our first test ensures that our application is working properly when the validation of the added todo entry fails. We can write this test by following these steps:

Create a title which has 101 characters.

Create a description which has 501 characters.

Create a new TodoDTO object by using our test data builder. Set the title and the description of the object.

Execute a POST request to url ‘/api/todo’. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF-8′. Transform the created TodoDTO object into JSON bytes and send it in the body of the request.

Verify that the HTTP status code 400 is returned.

Verify that the content type of the response is ‘application/json’ and its content type is ‘UTF-8′.

Fetch the field errors by using the JsonPath expression $.fieldErrors and ensure that two field errors are returned.

Fetch all available paths by using the JsonPath expression $.fieldErrors[*].path and ensure that field errors about the title and description fields are found.

Fetch all available error messages by using the JsonPath expression $.fieldErrors[*].message and ensure that error messages about the title and description fields are found.

Verify that the methods of our mock object are not called during our test.

Test 2: Todo Entry Is Added to The Database

The second unit test ensures that our controller is working properly when a new todo entry is added to the database. We can write this test by following these steps:

Create a new TodoDTO object by using our test data builder. Set “legal” values to the title and description fields.

Create a Todo object which is returned when the add() method of the TodoService interface is called.

Configure our mock object to return the created Todo object when its add() method is called and a TodoDTO object is given as a parameter.

Execute a POST request to url ‘/api/todo’. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF-8′. Transform the created TodoDTO object into JSON bytes and send it in the body of the request.

Verify that the HTTP status code 200 is returned.

Verify that the content type of the response is ‘application/json’ and its content type is ‘UTF-8′.

Get the id of the returned todo entry by using the JsonPath expression $.id and verify that the id is 1.

Get the description of the returned todo entry by using the JsonPath expression $.description and verify that the description is “description”.

Get the title of the returned todo entry by using the JsonPath expression $.title and ensure that the title is “title”.

5 comments

Hi Petri. I saw your youtube videos as well and I am trying to implement this in my application, however I am getting some errors.

My controller has a dependency on SampleService class, so my text context declared this

However when I run the test, I am getting the below error.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.anand.validationservice.service.SampleService] is defined: expected single matching bean but found 2: sampleService,sampleServiceImpl

As you know sampleService is only an interface and I have sampleServiceImpl as the implementation of the same so why it is saying duplicate?

In order to resolve this – I completely took the text context off and instead used the following way to create the instance of sampleService in the setUP method sampleServiceMock = Mockito.mock(SampleService.class);

But it looks like the content type is not being set as I get the following error.

java.lang.AssertionError: Content type not set at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:39) at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:72) at org.springframework.test.web.servlet.result.ContentResultMatchers$1.match(ContentResultMatchers.java:75) at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141) at com.westernalliancebancorp.validationservice.web.controller.ValidationServiceRESTControllerTest.testGetSample(ValidationServiceRESTControllerTest.java:82) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

About your first problem: Does your application context configuration file configure a component scan for service packages? The error message indicates that this is the case (Spring found two beans: your mock and the actual service implementation). You can fix this by splitting the configuration as explained in the first part of the tutorial:

About your second problem: Unfortunately it is impossible to say why the test fails without seeing the code of the controller method and your app context configuration. If you can add them here, I can investigate the issue further.

A few questions though:

1) It seems that you are creating the mock correctly but how do you set it to the controller? 2) Are both of your tests failing?

Newsletter

Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

Email address:

Recent Jobs

No job listings found.

Join Us

With 1,240,600 monthly unique visitors and over 500 authors we are placed among the top Java related sites around. Constantly being on the lookout for partners; we encourage you to join us. So If you have a blog with unique and interesting content then you should check out our JCG partners program. You can also be a guest writer for Java Code Geeks and hone your writing skills!

Disclaimer

All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners. Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries. Examples Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.