Junit & Mockito with Spring Boot Rest Application

Junit & Mockito with Spring Boot Rest Application | We have a simple Spring boot Rest application having APIs to insert, update, delete, and select the employees from the database. Here is the link to the GitHub project of the spring boot rest application:- Spring Boot Rest APIs Code.

We are using the MySQL database for this Spring boot application. From the application.properties file, you can observe that we are using database “test1” with username “root” and password “root”. We will test this application using Junit & Mockito. The important classes of the application are given as follows:-

Model/Entity class (Employee.java)

package com.knowprogram.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Employee {

   @Id
   @GeneratedValue
   private Integer empId;
   private String empName;
   private Double empSal;

   // setter & getter methods
}

Service Class (IEmployeeService.java),

package com.knowprogram.service;

import java.util.List;

import com.knowprogram.model.Employee;

public interface IEmployeeService {

   public Employee saveEmployee(Employee e);

   public List<Employee> getAllEmployees();

   public Employee getOneEmployee(Integer id);

   public void deleteEmployee(Integer id);
}

Service Implementation Class (EmployeeServiceImpl.java),

package com.knowprogram.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.knowprogram.exception.EmployeeNotFoundException;
import com.knowprogram.model.Employee;
import com.knowprogram.repo.EmployeeRepository;
import com.knowprogram.service.IEmployeeService;

@Service
public class EmployeeServiceImpl implements IEmployeeService {
   
   @Autowired
   private EmployeeRepository repo;

   @Override
   public Employee saveEmployee(Employee e) {
      return repo.save(e);
   }

   @Override
   public List<Employee> getAllEmployees() {
      return repo.findAll();
   }

   @Override
   public Employee getOneEmployee(Integer id) {
      return repo.findById(id).orElseThrow(
         () -> new EmployeeNotFoundException("Employee not exist")
      );
   }

   @Override
   public void deleteEmployee(Integer id) {
      Employee e = getOneEmployee(id);
      repo.delete(e);
   }
}

RestController Class (EmployeeRestController.java),

package com.knowprogram.rest;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.knowprogram.model.Employee;
import com.knowprogram.service.IEmployeeService;

@RestController
@RequestMapping("/employee")
public class EmployeeRestController {

   @Autowired
   private IEmployeeService service;

   @PostMapping("/save")
   public ResponseEntity<String> saveEmployee(@RequestBody 
                                              Employee employee) {
      Integer empId = service.saveEmployee(employee).getEmpId();
      return ResponseEntity.ok("Employee saved " + empId);
   }

   @GetMapping("/all")
   public ResponseEntity<List<Employee>> getAllEmployee() {
      List<Employee> employees = service.getAllEmployees();
      return ResponseEntity.ok(employees);
   }

   @GetMapping("/{id}")
   public ResponseEntity<Employee> getOneEmployee(@PathVariable 
                                                   Integer id) {
      Employee emp = service.getOneEmployee(id);
      return ResponseEntity.ok(emp);
   }

   @DeleteMapping("/remove/{id}")
   public ResponseEntity<String> deleteEmployee(@PathVariable 
                                                Integer id) {
      service.deleteEmployee(id);
      return ResponseEntity.ok("Employee Deleted");
   }

   @PutMapping("/modify/{id}")
   public ResponseEntity<String> updateEmployee(@PathVariable 
                 Integer id, @RequestBody Employee employee) {
      Employee emp = service.getOneEmployee(id);
      emp.setEmpName(employee.getEmpName());
      emp.setEmpSal(employee.getEmpSal());
      service.saveEmployee(employee);
      return ResponseEntity.ok("Employee Updated!!");
   }
}

Observe the code in RestController. We are going to write test-case/test-method for the given RestController methods.

Note:- For every spring boot application, the spring-boot-starter-test is provided by default that supports JUnit 3, 4, 5, and Mockito 3. Therefore we don’t need to and any extra dependencies in our pom.xml file.

Create Properties File

First of all, let us create a separate properties file so that we can have separate properties for the testing environment. We are going to use the same configurations, therefore, we have just copied and pasted the file & they contain exact same information. But you can use your own configurations. In our application, we have created a separate properties file with the name “application-test.properties”.

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test1
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

Naming Convention for Test Class & Methods

The naming convention for test class is:- Test<Class-Name> and similarly for test methods:- test<Method-Name>().

For example, we want to test the EmployeeRestController so we will create a class for testing having name TestEmployeeRestController. The EmployeeRestController class contains methods saveEmployee(), getAllEmployee(), getOneEmployee() and so we will create test methods with name:- testSaveEmployee(), testGetAllEmployee(), testGetOneEmployee(), and e.t.c.

Create Test Class

In src/test/java create a class with name TestEmployeeRestController in “com.knowprogram” package.

package com.knowprogram;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@TestPropertySource("classpath:application-test.properties")
public class TestEmployeeRestController {

   @Autowired
   private MockMvc mockMvc;

   // test methods
}

What is @SpringBootTest? Since our class is a test class therefore we have to apply @SpringBootTest to our class. The @SpringBootTest annotation indicates that the current class is a test case and it contains testing code. Spring environment identifies test class with the help of @SpringBootTest.

What is webEnvironment = WebEnvironment.MOCK and why do we need it?
When we run the starter class (in our case SpringBootJunitTestApplication class) then it not only creates the spring container (AnnotationConfigServletWebServerApplicationContext) but also creates required objects like database connections, profiles, server setup, and other required things.

Whereas when we execute the test class then we won’t run the starter class, we will directly run the test class (TestEmployeeRestController class). For the testing environment also we need a spring container, database connections, server startup, and other required things. This is where Mockito comes into the picture. The spring container, required environment, and setup will be provided by the Mockito.

@AutoConfigureMockMvc activates support for the HTTP protocol, properties detection, component scanning, profiles, and e.t.c. To provide new properties file with the location we have to use:- @TestPropertySource("classpath:application-test.properties")

Develop The Test Method

Now our class is ready and we can start developing the test methods to test the methods of EmployeeRestController class. First, let us develop the test case for the saveEmployee() method. As per naming convention, the method name will be testSaveEmployee(). The URL for saveEmployee() is “/employee/save” and it is a post method.

package com.knowprogram;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@TestPropertySource("classpath:application-test.properties")
public class TestEmployeeRestController {

   @Autowired
   private MockMvc mockMvc;

   @Test
   public void testSaveEmployee() throws Exception {
      // 1. create dummy request
      MockHttpServletRequestBuilder request = 
           MockMvcRequestBuilders
           .post("/employee/save")
           .contentType(MediaType.APPLICATION_JSON)
           .content("{\"empName\":\"Jhon\",\"empSal\":5300.0}");

      // 2. execute request and get result
      MvcResult result = mockMvc.perform(request).andReturn();

      // 3. read response
      MockHttpServletResponse response = result.getResponse();

      // 4. Test using assert Method
      //      is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());

      if (!response.getContentAsString().contains("Employee saved ")) {
         fail("SAVE EMPLOYEE NOT WORKING");
      }
   }

}

Steps for writing test methods in spring-boot:-

1. Create a dummy request.
2. Execute the request and get the result.
3. Read response.
4. Test using the assert method.

1. Create a dummy request:- This step code may vary according to the request URL, and request type. To create a request there are several methods defined in MockMvcRequestBuilders abstract class. Below are the important methods to create the MockHttpServletRequestBuilder objects for the different request types.

MethodDescription
get(URI uri)Create a MockHttpServletRequestBuilder for a GET request.
post(URI uri)Create a MockHttpServletRequestBuilder for a POST request.
put(URI uri)Create a MockHttpServletRequestBuilder for a PUT request.
delete(URI uri)Create a MockHttpServletRequestBuilder for a DELETE request.

2. Execute the request and get the result:- After creating the MockHttpServletRequestBuilder object we have to call perform() method and to get the result we have to call andReturn() method. This step could be the same in most of the test methods.

3. Read response:- After getting the MvcResult we have to read the response using the getResponse() method. The getResponse() method returns MockHttpServletResponse object. This step also could be the same in many test methods.

4. Test using assert method:- On the response object we can call several methods and pass them in assert methods. The important methods are:-

MethodDescription
getStatus()It returns the status code.
getContentAsString()It returns the content of the response body as a String.

In our case, the request is POST type therefore we have called post(url) and then passed contentType() and then content which should be inserted/saved.

If the request type is “GET” then we can simply call:- MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("url"); Because in that case, the content type and content need not be sent along with the request. We will see them in other test methods.

Develop Remaining Other Test Methods

package com.knowprogram;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@TestPropertySource("classpath:application-test.properties")
public class TestEmployeeRestController {

   @Autowired
   private MockMvc mockMvc;

   @Test
   // @Disabled
   public void testSaveEmployee() throws Exception {
      // 1. create dummy request
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders
          .post("/employee/save")
          .contentType(MediaType.APPLICATION_JSON)
          .content("{\"empName\":\"Jhon\",\"empSal\":5300.0}");

      // 2. execute request and get result
      MvcResult result = mockMvc.perform(request).andReturn();

      // 3. read response
      MockHttpServletResponse response = result.getResponse();

      // 4. Test using assert Method
      // is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());

      if (!response.getContentAsString().contains("Employee saved ")) {
         fail("SAVE EMPLOYEE NOT WORKING");
      }
   }

   @Test
   @Disabled
   public void testGetAllEmployee() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders.get("/employee/all");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());
      assertEquals(MediaType.APPLICATION_JSON_VALUE,
                   response.getContentType());
      if (response.getContentAsString().length() <= 0) {
         fail("No Data Provided");
      }
   }

   @Test
   @Disabled
   public void testGetOneEmployeeExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders.get("/employee/4");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());
      assertEquals(MediaType.APPLICATION_JSON_VALUE, 
                   response.getContentType());
      if (response.getContentAsString().length() <= 0) {
         fail("No Data Provided");
      }
   }

   @Test
   @Disabled
   public void testGetOneEmployeeNotExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders.get("/employee/50");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 404?
      assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());
      if (!response.getContentAsString().equals("Employee not exist")) {
         fail("May be data exist, Please check again");
      }
   }

   @Test
   @Disabled
   public void testDeleteEmployeeExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders.delete("/employee/remove/1");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();
      // is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());
   }

   @Test
   @Disabled
   public void testDeleteEmployeeNotExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders.delete("/employee/remove/100");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 404?
      assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());
      if (!response.getContentAsString().equals("Employee not exist")) {
         fail("May be data exist, Please check again");
      }
   }

   @Test
   @Disabled
   public void testUpdateEmployeeExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders
            .put("/employee/modify/3")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"empName\":\"Amelia\",\"empSal\":9999.0}");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 200?
      assertEquals(HttpStatus.OK.value(), response.getStatus());
      assertEquals("Employee Updated!!", response.getContentAsString());
   }

   @Test
   @Disabled
   public void testUpdateEmployeeNotExist() throws Exception {
      MockHttpServletRequestBuilder request = 
          MockMvcRequestBuilders
            .put("/employee/modify/100")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"empName\":\"Amelia\",\"empSal\":9989.0}");
      MvcResult result = mockMvc.perform(request).andReturn();
      MockHttpServletResponse response = result.getResponse();

      // is status code 404?
      assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());
      if (!response.getContentAsString().equals("Employee not exist")) {
         fail("May be data exist, Please check again");
      }
   }
}

To run TestEmployeeRestController:- Run As => Junit Test. See the application code at GitHub:- SpringBootRestJunitMockito.

In the testGetAllEmployee() method, we have a GET type request therefore we have called the get() method. In response, it should contain status code 200 and the body should have “application/json” media type.

To test getOneEmployee() we have created two test methods:- testGetOneEmployeeExist(), and testGetOneEmployeeNotExist(). The testGetOneEmployeeExist() method test whether the employee exists with the given employee id or not? If the employee exists then the test will be successful else the test will fail.

Whereas the testGetOneEmployeeNotExist() method test whether the employee with the given employee id doesn’t exist or not? If the employee doesn’t exist then the test will be successful else if the employee exists then the test will fail. When an employee doesn’t exist then Rest API will give status code 404 and throw EmployeeNotFoundException which gives the message “Employee not exist”.

For other methods also we have tested two cases:- a) when an employee exists with a given id & b) when an employee doesn’t exist with the given id.

If we don’t want to run any test method in that case we can use @Disabled on those methods. For example, when there are no entries in the database then the test methods for get, get all, update, and delete methods will always fail. Therefore to don’t run their test methods we can use @Disabled annotation on their test methods. Also see:- Working With Redis Database Using Spring Boot

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *