RESTful Web Services Unit Testing with Spring Boot


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.

Advertisements

 

UPDATE: New version of this article JUnit Testing of SpringBoot 2 REST API with JUnit 5 and Java 10 is available

 

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

 

Project Setup

You just need two additional dependencies in our RESTFul Web Service project’s pom.xml to start writing unit test cases.

  1. spring-boot-starter-test: Spring Boot Test Framework with libraries including JUnit, Mockito etc.
  2. 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.
Advertisements

Unit Testing Steps

Every unit test case should have following three steps:

  1. Preparation
  2. Execution
  3. 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:

  1. Returns HTTPStatus as OK
  2. Returns a NOT NULL Employee object which we stubbed in preparation step
  3. 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:

  1. Returns HTTPStatus as NOT_FOUND
  2. Returns a NULL Employee object as nothing is stubbed for service layer
  3. 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

Advertisements

Next Step

Learn about: