Unit Testing in Spring Boot for RESTful Web Services

How to Implement RESTful Service Unit Tests in Spring Boot?

Hakan SANDER
The Startup
9 min readApr 24, 2020

--

Prerequisites

  • Good understanding of Java
  • Knowledge of RESTful Web Services
  • Knowledge of Maven
  • Knowledge of Spring Boot

Tools Needed

What Is Unit Testing?

Unit testing is a software testing type. Each individual unit of a software are tested by isolating them from each other. For instance, in a Spring Boot application, you should isolate the service, controller, repository layers from each other. When you implement a unit test for a controller, it should not make a service call to real web service but instead it should make a call to mock service. Software developers implement the unit tests during the development phase of a project.

Why Unit Tests Are Relevant?

When you develop an application, the design and implementation of some features might change at some point. When the app is already tested and if you modify some lines, you might need to test the application just from the beginning in order to feel safe that everything is okay. However, this process is very time consuming.

Instead, when you have unit tests for your packages (service, repository, controller e.t.c), that covers each and every cases of your application you ought to feel confident. When the application is modified, the only thing you have to do is running these cases again to be sure that there is not any undesired impacts on the code. In other words, unit tests provides a way for the safe refactoring of the code.

In addition, when you follow Test Driven Development (TDD) technique, you implement the unit tests first. The unit tests fail due to the missing implementation of the necessary code. Then, you implement the missing features and run these tests again until the requirements are met. Hence, unit tests are also very crucial for creating a high quality product via Agile methodology.

Finally, the unit tests provides an always up-to-date documentation for your application. When you work in a large project, most of the times writing the document of the project is avoided due to various reasons. However, since the unit tests are always refactored when even a minor code snippet is changed, it provides an up-to-date documentation especially for the newcomer developers.

For instance, when you look at the code, you can say “Okay, when the input is customer A, and the user data of A exists in the database, then the program will return HTTP200 with the customer details of A”. However, “When the input is customer B, and the user data of B does not exist in the database, the program will return HTTP204 with the no data found warning to the client”. You can easily understand all such cases by only inspecting the unit tests of the code which is a huge advantage.

Unit Testing Frameworks in Java

In this tutorial, we will use Junit and Mockito for the unit tests.

JUnit is an open source unit testing framework for Java. This framework is commonly used to write unit tests for Spring Boot applications.

Mockito is one of the most popular mocking frameworks for Java. You can create mock objects and determine the behaviour of these mock objects. For instance, you can create a mock server object and configure the behaviour of this server according to your unit testing needs. Mockito greatly simplifies the implementation of unit tests for the classes with external dependencies .

In this tutorial, we will use Junit to write repeatable unit tests and Mockito will be used for mocking external dependencies.

Static Responses From the RESTful Web Service

We will use the following static JSON responses from our RESTful web service. Instead of making a real call to the available web service, we will create mock responses using these static JSON files.

Success response of the web service when data is found for the given phone number
When the no data found for the given phone number, the service returns the given response

Why Static Response Files Have Been Used?

In order to implement the tests, we should mock the return values of the service calls. In order to do that, you can create objects via constructors or using the setter methods, you can fill the fields according to the test cases. The second way would be using response Strings and mapping the responses accordingly using ObjectMapper. The second approach has been used in this tutorial. The response files are read from the json files for having a more readable code since the json responses might be so complex. In this case, copying and pasting these responses into the code would not be proper.

InvoiceService.java Implementation

In the code snippet below, we are making a rest service client call via restTemplate to a RESTful web service. If the returned data successfully maps to the BillingInquiry model, then the body of the ResponseEntity model is returned to the controller successfully. However, if there is a problem due to various reasons (The RESTful web service is not working properly, the requestHeader content is wrong e.t.c), then the program will throw an Exception and it will continue from the catch block. In the unit test, we should cover each of these cases.

InvoiceService.java Code Snippet

InvoiceService Unit Test Implementation

@RunWith -> This annotation is needed for enabling the usage of spring boot features such as @Autowire and @MockBean

@RestClientTest -> We use Jackson to map JSON to our Java Objects. We also need mock rest service and auto configuration of restTemplateBuilder. This annotation provides the auto configuration of Jackson. In addition, it adds the already configured MockRestServiceServer and RestTemplateBuilder to the ApplicationContext

InvoiceServiceTest.java Code Snippet

We need to check HTTP 200 and HTTP 204 cases in the service unit test. Also, we should implement a unit test for the catch block.

HTTP 200 case is tested via the following code snippet. In this scenario, we create a mock response object. We configure the mockServer to expect only 1 GET request and we configure it to give response of our static response.

mockServer.verify() checks whether the fields are returned as expected.

InvoiceService Success Case

Although this unit test is successfully passed, as you can see in the below image, the service is not fully covered. It means, we are not checking yet whether each functionality is working as we expected them to work or not. Therefore, we should implement additional unit tests. The green lines of the image shows the covered lines and the red lines show the uncovered parts. In this case, the catch block is not covered. Also, we should add the unit tests for other possibilities. In our case, this is HTTP204 no data found. In a real application, there might be a lot of different situations and you should implement unit tests for each of them separately.

Partial coverage of invoiceService.java

In order to test the catch block of the invoiceService, following unit test is implemented. The mockServer is configured to throw a RunTimeException when a REST service call is made. Then, when the service call is made in line 11, the catch block is covered.

Exception unit test

When we also implement the HTTP204 case (please visit the source code), the service unit tests cover all the cases and the lines in the source code and they are successfully passed. The output of the service unit test package is given in the images below.

Service unit tests are successfully passed
Service implementation coverage

For the exception handling, we have manually created an invoiceResponse object which is not a suggested way. In Spring Boot, there are very good mechanism to handle to exceptions. On the other hand, the exception is implemented in this way in order to show the unit testing of exceptions basically.

InvoiceController.java Implementation

We will implement the unit test for the given code snippet. There are two different cases that we need to consider while implementing the unit tests.

When the entered phone number retrieves the data from the back-end Service successfully, the controller should return the invoiceResponse object to the client.

When the data has not been returned successfully, then the Response object that contains HttpStatus.NO_CONTENT should be returned to the customer.

InvoiceController.java Code Snippet

There might also be different cases other than no data found and success. For the simplification, I have only created two different cases in this tutorial and implemented unit tests for only two of them.

InvoiceController.java Unit Test Implementation

The controller unit test implementation code snippet is given below. The annotation explanations are:

@RunWith -> This annotation is needed for enabling the usage of spring boot features such as @Autowire and @MockBean

@WebMvcTest -> Using this annotation, Spring Boot creates only the relevant beans for testing a controller through an ApplicationContext

@MockBean -> This annotation provides adding bean objects into the ApplicationContext. If the beans already exist in the ApplicationContext, then they will be replaced with the annotated ones.

InvoiceControllerTest.java Code Snippet

In order to implement the HTTP 200 success case of the Controller, following test is implemented. When the entered phone number is in correct format, and the data record exists, then mock service should return the previously configured success response object which is the mockInvoiceResponse. In order to do it, we read the static success response file from the resources, then we map the fields into the mockInvoiceResponse. Finally, we configure the response via the 11th and 12th lines. We tell the program that “When we make a service call, the service should return the mockInvoiceResponse”. Then, a REST service call is performed through mockMvc.perform to the controller url, and we check the returned response and search for the statusCode 200.

HTTP200 Success Case Unit Test

When we only run this unit test, we can observe that the test is sucessfully passed in the following image. It took 303ms to run. Unit tests ideally should run as fast as possible because you might have hundreds of unit tests in your project.

Unit test successfully passes

In order to check the missing data case, we may implement the following unit test.

No data found case for controller

When we run this unit test, we can see that it also successfully passes. Furthermore, all the lines are green now which means we have fully covered the Controller cases.

Full Coverage of Controller

We can also check whether some fields are returned other than status code as we have expected but for the simplification, I will not mention them in this tutorial.

Final Output

When we complete the implementation of our unit tests, we can run the test package with coverage. In Intellij Idea, you can right click the java package inside the test package and select “Run All Tests with Coverage”. When we select it, the following output is obtained.

Final unit test coverage output

As you can see it, each of the classes, lines and methods are covered. Since it is a very small project, we have been able to reach such a very successful result. However, for the large-scale projects, reaching coverage results greater than 80% would be good because there might be cases that implementing unit tests would be very challenging.

What’s Next?

That was the first tutorial for me to publish on this platform. I will keep writing new tutorials mainly about Java, Spring Boot, Microservices, Container Technologies and also other Back-end Development and Computer Science topics.

Source Code

Full source code is available on GitHub

--

--

Hakan SANDER
The Startup

Software Engineer at Trendyol Group (Istanbul Technical University — Computer Engineering Graduate)