This article explains how Unit Testing of RESTful Web Services is performed using Spring Boot Test framework. It uses MockMvc to provide Spring MVC infrastructure without starting the HTTP Server. SpringRunner and @WebMvcTest provide rest of the environment for unit testing.
It will unit test the RESTful Web Services for CRUD operations explained in RESTful Web Service CRUD Operations with Spring Boot (If you are new to Spring Boot and Restful Web Services, better to read this first). At the end of this article, you will get the complete source code of Restful Web Services for CRUD operations along with their unit test cases.
Technology Stack
Technology stack used in this example is:
- Spring Boot 1.4.1.RELEASE
- Spring Data JPA
- Database – PostgreSQL
- JDK 8
RESTful Web Services Integration Testing with Spring Boot
RESTful Web Service CRUD Operations with Spring Boot
RESTful Web Services Authentication and Authorization
Project Setup
You just need two additional dependencies in our RESTFul Web Service project’s pom.xml to start writing unit test cases.
- spring-boot-starter-test: Spring Boot Test Framework with libraries including JUnit, Mockito etc.
- Google’s gson library: For converting JSON to Object and vice versa.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.1.RELEASE</version> </parent> <groupId>com.bytestree.restful</groupId> <artifactId>spring-restful-service-unit-test</artifactId> <version>1.0.0-SNAPSHOT</version> <name>Spring Restful Service Unit Testing</name> <description>Unit testing of Spring Restful Services using mock</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Unit Test Class Configuration
Let’s start writing the Test class responsible for unit testing our RESTful Web Service. First, take a look at class level configuration:
//import statements @RunWith(SpringRunner.class) @WebMvcTest(EmployeeController.class) public class EmployeeControllerTest { @Autowired private MockMvc mockMvc; @MockBean EmployeeService empService; // other class level variables and methods... }
@RunWith(SpringRunner.class)
: It is an alias for SpringJUnit4ClassRunner. It will add Spring TestContext Framework support.@WebMvcTest
: Auto configure Spring MVC infrastructure and MockMvc. It will only scan beans related to Web layer, like @Controller, @ControllerAdvice, WebMvcConfigurer etc. This is useful because we are interested only in web layer when unit testing Controller classes. In our case, we are limiting it to EmployeeController.class.MockMvc
: Provide Spring MVC infrastructure without starting the HTTP Server.@MockBean
: Provides Mockito mock bean for a given class’s instance. Normally used to inject mock dependency. In our case, a mock instance of EmployeeService is injected because class under test (EmployeeController) requires it.
Unit Testing Steps
Every unit test case should have following three steps:
- Preparation
- Execution
- Verification
Preparation
The preparation step includes contents in method with @Before and @BeforeClass annotation. Here we set all data required to execute a method under test. Also, set what to return when a method on a mock object is called. In other words, all the stubbing is done here. For multi-layer application remember that all data or operations below the layer under test should be mocked or stubbed. For example, if you are testing a Controller layer which calls Service layer to complete its operation, mock or stub the Service layer.
Execution
The execution step executes the actual method under test. In our example, the mockMvc will create a request which will cause a method in a controller class to execute.
Verification
In verification step, we check the expected behavior of the method under test. If a method returns something then simple way is to verify the returned object. In addition to it, also check if any inner method is called or not. In mockito, use verify() method to check this.
Writing Unit Test Cases
With all the basics known, now let’s start writing a first test case. The RESTful Web Service we are going to unit test is of CRUD operations. Let’s take a look at Read operation(getEmployee() method). In Read operation, two scenarios can be possible, positive and negative. Positive is we find the employee we are looking for and negative is we didn’t find what we are looking.
The method under test is:
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public ResponseEntity<Employee> getEmployee(@PathVariable("id") Long id) { Employee employee = empService.getById(id); if (employee == null) { logger.debug("Employee with id " + id + " does not exists"); return new ResponseEntity<Employee>(HttpStatus.NOT_FOUND); } logger.debug("Found Employee:: " + employee); return new ResponseEntity<Employee>(employee, HttpStatus.OK); }
Below are the key steps this method is performing:
- To get the Employee it makes one call to Service layer which is empService.getById(id)
- It returns ResponseEntity with Employee object with HTTPStatus OK/ NOT_FOUND based on service response.
We are going to verify these things in verification step of our unit test cases.
First test the positive scenario.
Unit test positive scenario
@Test public void testGetEmployee() throws Exception { // prepare data and mock's behaviour Employee empStub = new Employee(1l, "bytes", "tree", "developer", 12000); when(empService.getById(any(Long.class))).thenReturn(empStub); // execute MvcResult result = mockMvc .perform(MockMvcRequestBuilders.get(URL + "{id}", new Long(1)) .accept(MediaType.APPLICATION_JSON_UTF8)) .andReturn(); // verify int status = result.getResponse().getStatus(); assertEquals("Incorrect Response Status", HttpStatus.OK.value(), status); // verify that service method was called once verify(empService).getById(any(Long.class)); Employee resultEmployee = TestUtils.jsonToObject(result.getResponse() .getContentAsString(), Employee.class); assertNotNull(resultEmployee); assertEquals(1l, resultEmployee.getId().longValue()); }
Preparation
The method under test makes a call to service layer by calling empService.getById(id). Therefore in the preparation step, stub what a service layer should return. The empService in test class is mock of Service layer at the class level. We are setting here what getById() method should return when called on this mock instance. It is nothing but a new Employee object we created in the test method.
Execution
We are using mockMvc object’s perform method to create a mock request with “id” parameter. It returns ResultActions, and by calling andReturn() we get MvcResult which provides access to the result of the executed request. Note that the method under test uses HTTP GET request, so we should use get method from MockMvcRequestBuilders.
Verification
We should verify that after execution, the MvcResult:
- Returns HTTPStatus as OK
- Returns a NOT NULL Employee object which we stubbed in preparation step
- One call to service layer’s empService.getById(id) method.
In above code, you can find all these checks were made after getting the MvcResult.
Unit test negative scenario
Here we test the scenario when the required object is not found. First, check the code and then our basic steps for unit testing.
@Test public void testGetEmployeeNotExist() throws Exception { // prepare data and mock's behaviour // Not Required as employee Not Exist scenario // execute MvcResult result = mockMvc .perform(MockMvcRequestBuilders.get(URL + "{id}", new Long(1)) .accept(MediaType.APPLICATION_JSON_UTF8)) .andReturn(); // verify int status = result.getResponse().getStatus(); assertEquals("Incorrect Response Status", HttpStatus.NOT_FOUND.value(), status); // verify that service method was called once verify(empService).getById(any(Long.class)); Employee resultEmployee = TestUtils.jsonToObject(result.getResponse() .getContentAsString(), Employee.class); assertNull(resultEmployee); }
Preparation
This step is empty as we are testing scenario when the required object is not found. Hence no need to stub the return object.
Execution
This is same as above scenario.
Verification
We should verify that after execution, the MvcResult:
- Returns HTTPStatus as NOT_FOUND
- Returns a NULL Employee object as nothing is stubbed for service layer
- One call to service layer’s empService.getById(id) method.
These steps should be modified based on what operations the method under test is performing. Below are code snippets of test cases of other CRUD operations:
Unit Testing Add operation
@Test public void testAddEmployee() throws Exception { // prepare data and mock's behaviour Employee empStub = new Employee(1l, "bytes", "tree", "developer", 12000); when(empService.save(any(Employee.class))).thenReturn(empStub); // execute MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(URL) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .content(TestUtils.objectToJson(empStub))).andReturn(); // verify int status = result.getResponse().getStatus(); assertEquals("Incorrect Response Status", HttpStatus.CREATED.value(), status); // verify that service method was called once verify(empService).save(any(Employee.class)); Employee resultEmployee = TestUtils.jsonToObject(result .getResponse().getContentAsString(), Employee.class); assertNotNull(resultEmployee); assertEquals(1l, resultEmployee.getId().longValue()); }
Unit Testing Update operation
@Test public void testUpdateEmployee() throws Exception { // prepare data and mock's behaviour // here the stub is the updated employee object with ID equal to ID of // employee need to be updated Employee empStub = new Employee(1l, "bytes", "tree", "developer", 12000); when(empService.getById(any(Long.class))).thenReturn(empStub); // execute MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put(URL) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .content(TestUtils.objectToJson(empStub))) .andReturn(); // verify int status = result.getResponse().getStatus(); assertEquals("Incorrect Response Status", HttpStatus.OK.value(), status); // verify that service method was called once verify(empService).save(any(Employee.class)); }
Unit Testing Delete operation
@Test public void testDeleteEmployee() throws Exception { // prepare data and mock's behaviour Employee empStub = new Employee(1l); when(empService.getById(any(Long.class))).thenReturn(empStub); // execute MvcResult result = mockMvc.perform(MockMvcRequestBuilders.delete(URL + "{id}", new Long(1))) .andReturn(); // verify int status = result.getResponse().getStatus(); assertEquals("Incorrect Response Status", HttpStatus.GONE.value(), status); // verify that service method was called once verify(empService).delete(any(Long.class)); }
Hope this clarified the basics on how to unit test RESTful Web Services. Refer below source code to get the complete project of unit test cases for all CRUD operations in RESTFul Web Service.
Source Code
The complete source code to unit test all CRUD operations in RESTful Web Services is available on GitHub
Next Step
Learn about: