Monday, September 1, 2025

Spring Boot Unit Test For REST API

In this article we'll see how to unit test Spring Boot REST API. As the name "unit test" itself suggests classes should be tested in isolation so in this tutorial we'll write unit tests for Controller class.

Please refer this post- Spring Boot + Data JPA + MySQL REST API CRUD Example to see the Controller class for which we are going to write unit tests.

Annotations and Classes used for writing unit tests in Spring Boot

First of all, ensure spring-boot-starter-test dependency is included. If you are using Maven then this dependency can be included in pom.xml like this.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

This starter bundles together common testing libraries like JUnit, Mockito, AssertJ, Spring Boot Test etc.

@WebMvcTest Annotation

It is a specialized annotation that can be used for a Spring MVC test that focuses only on Spring MVC components. Using this annotation disables the full auto-configuration and only enables auto-configuration that is relevant to MVC tests. Similarly, component scanning is limited to beans annotated with:

  • @Controller
  • @ControllerAdvice
  • @JsonComponent

It focuses on testing controllers and other web-related components in isolation, without loading the entire Spring application context.

MockMvc class

MockMvc provides support for testing Spring MVC applications. With MockMvc you don't require to run a web server, MockMvc simulates the full Spring MVC request handling using mock request and response objects.

@MockitoBean annotation

@MockitoBean annotation in Spring Framework is used in test classes to create a mock object. Since we are testing Controller class in isolation so any controller's dependencies on service layer, repository layer is handled using mock instances.

Note that Spring framework leverages the Mockito library to create and manage the mock objects which are used to create stub methods and verify returned values.

Note that @MockitoBean annotation was introduced in Spring Framework 6.2 it replaces @MockBean, which is now deprecated.

For example, if you want to test the UserController class then basic setup using the above mentioned annotations and classes would be as given below-

@WebMvcTest(UserController.class)
public class UserControllerTest {
	@Autowired
	private MockMvc mockmvc;
	@MockitoBean
	private UserService userService;

	@Test
	public void testAddUserReturningBadRequest() throws Exception{
		…
		…
  }
}

Writing unit tests for Spring Boot REST API

Here we want to write unit tests for UserController class which has dependency on UserService and provides methods for adding user (POST), getting all users and getting user by id (GET), updating user (PUT) and delete user by id (DELETE).

If you analyze the code for adding a new user, scenarios you can test are-

  1. If User bean fails validation; which results in MethodArgumentNotValidException being thrown.
  2. If same user is added again; which results in UserAlreadyExistsException being thrown.
  3. All values are correct and the new user is added. Response status sent is 201 (Created) and created user is also returned as response body.

Here is the method that is written in UserController annotated with @PostMapping

// Insert user record
@PostMapping
public ResponseEntity<User> addUser(@Valid @RequestBody User user) {
  User createdUser = userService.addUser(user);
  return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}

If you have to write unit tests for the above mentioned scenario 1 and 3 then that is how you can write the tests.

@WebMvcTest(UserController.class)
public class UserControllerTest {
	@Autowired
	private MockMvc mockmvc;
	@Autowired
	private ObjectMapper objectMapper;
	@MockitoBean
	private UserService userService;
	
	@Test
	public void testAddUserReturningBadRequest() throws Exception{
		User user = new User();
		user.setFirstName("");
		user.setLastName("Tiwari");
		user.setUserType("Platinum");
		user.setStartDate(LocalDate.of(2025, 8, 26));
		
		mockmvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON)
				 .content(objectMapper.writeValueAsString(user)))
        		 .andExpect(status().isBadRequest())
        		 .andDo(print());
	}

	@Test
	public void testAddUserReturningCreated() throws Exception{
		User user = new User();
		user.setFirstName("Ram");
		user.setLastName("Tiwari");
		user.setUserType("Platinum");
		user.setStartDate(LocalDate.of(2025, 8, 26));
		// sending Id also
		user.setUserId(1);
		Mockito.when(userService.addUser(any(User.class))).thenReturn(user);
		mockmvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON)
				 .content(objectMapper.writeValueAsString(user)))
        		 .andExpect(status().isCreated())
        		 .andExpect(jsonPath("$.firstName").value("Ram"))
        		 .andExpect(jsonPath("$.userId").value(1))
        		 .andDo(print());        		 		
	}
}

Important points to note here are-

  1. Method testAddUserReturningBadRequest() tests the scenario, if validation fails and method testAddUserReturningCreated() tests the scenario when new user is added.
  2. With Post mapping, you need to send the resource as part of request body in the form of JSON. For that ObjectMapper, provided by Jackson library, is used which converts Java object to and from JSON.
  3. perform method of MockMvc is used to perform a request that uses any Http method (in this case POST), send the type of content and the request body.
  4. You can define expectations by appending one or more andExpect(..) calls after performing a request. These andExpect() methods use MockMvcResultMatchers to assert on the HTTP status, response headers, and response body content. For example andExpect(status().isCreated()), andExpect(jsonPath("$.FIELD_NAME").value("EXPECTED_VALUE"))
  5. andDo(print()) is used to print the details of a MockHttpServletRequest and MockHttpServletResponse to the console during a test execution.
  6. In the method testAddUserReturningCreated(), Mockito.when() is used to specify the return value when the method is called on the mock object. In the above example it specifies; when userService.addUser() is called from the UserController then use the mocked userService instance and return the user object itself.
    Mockito.when(userService.addUser(any(User.class))).thenReturn(user);
    
  7. Notice the use of any() in the userService.addUser() call. It specifies that any user object will do. If you want the exact same user instance to be used then ensure that the User class implements hashCode() and equals() method to enforce object identity. Then you can use Mockito.when() as given below.
    Mockito.when(userService.addUser(user)).thenReturn(user);
    

Same way you can write unit tests for other methods in the UserController class. Here is the complete UserControllerTest class.

UserControllerTest.java

import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netjstech.exception.ResourceNotFoundException;
import com.netjstech.model.User;
import com.netjstech.service.UserService;

@WebMvcTest(UserController.class)
public class UserControllerTest {
  @Autowired
  private MockMvc mockmvc;
  @Autowired
  private ObjectMapper objectMapper;
  @MockitoBean
  private UserService userService;
  
//  @Test
//  void test() {
//    fail("Not yet implemented");
//  }
  
  
  @Test
  public void testAddUserReturningBadRequest() throws Exception{
    User user = new User();
    user.setFirstName("");
    user.setLastName("Tiwari");
    user.setUserType("Platinum");
    user.setStartDate(LocalDate.of(2025, 8, 26));
    

    mockmvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON)
         .content(objectMapper.writeValueAsString(user)))
             .andExpect(status().isBadRequest())
             .andDo(print());    
  }

  @Test
  public void testAddUserReturningCreated() throws Exception{
    User user = new User();
    user.setFirstName("Ram");
    user.setLastName("Tiwari");
    user.setUserType("Platinum");
    user.setStartDate(LocalDate.of(2025, 8, 26));
    // sending Id also
    user.setUserId(1);
    Mockito.when(userService.addUser(any(User.class))).thenReturn(user);
    mockmvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON)
         .content(objectMapper.writeValueAsString(user)))
             .andExpect(status().isCreated())
             .andExpect(jsonPath("$.firstName").value("Ram"))
             .andExpect(jsonPath("$.userId").value(1))
             .andDo(print());                 
  }
  
  @Test
  public void testGetAllUsers() throws Exception{
    List<User> userList = new ArrayList<>();
    User user1 = new User();
    user1.setUserId(1);
    user1.setFirstName("Ram");
    user1.setLastName("Tiwari");
    user1.setUserType("Platinum");
    user1.setStartDate(LocalDate.of(2025, 8, 26));
    
    User user2 = new User();
    user2.setUserId(2);
    user2.setFirstName("Shyam");
    user2.setLastName("Sharma");
    user2.setUserType("Gold");
    user2.setStartDate(LocalDate.of(2025, 7, 22));
    userList.add(user1);
    userList.add(user2);
    Mockito.when(userService.getAllUsers()).thenReturn(userList);
    
    mockmvc.perform(get("/user/allusers"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].firstName").value("Ram"))
        .andExpect(jsonPath("$[1].firstName").value("Shyam"))
        .andDo(print());
  }
  
  @Test
  public void testGetUserReturningResourceNotFound() throws Exception{
    int userId = 1;
    
    Mockito.when(userService.getUserById(userId)).thenThrow(new ResourceNotFoundException("User with ID " + userId + " not found"));
    
    mockmvc.perform(get("/user/"+userId)).andExpect(status().isNotFound())
    .andExpect(content().json("{\"message\": \"Resource Not Found\"}"))
    .andExpect(jsonPath("$.exceptionMessage").value("User with ID " + userId + " not found"))
    .andDo(print());
    
  }
  
  @Test
  public void testGetUserReturningOK() throws Exception{
    int userId = 1;
    User user = new User();
    user.setUserId(1);
    user.setFirstName("Ram");
    user.setLastName("Tiwari");
    user.setUserType("Platinum");
    user.setStartDate(LocalDate.of(2025, 8, 26));
    Mockito.when(userService.getUserById(userId)).thenReturn(user);
    
    mockmvc.perform(get("/user/"+userId)).andExpect(status().isOk())
    .andExpect(jsonPath("$.firstName").value("Ram"))
    .andExpect(jsonPath("$.lastName").value("Tiwari"))
    .andDo(print());
  }
  
  @Test
  public void testUpdateUserReturningResourceNotFound() throws Exception{
    User user = new User();
    user.setUserId(100);
    user.setFirstName("Ram");
    user.setLastName("Tiwari");
    user.setUserType("Platinum");
    user.setStartDate(LocalDate.of(2025, 8, 26));
    
    Mockito.when(userService.updateUser(user))
          .thenThrow(new ResourceNotFoundException("User with ID " + user.getUserId() + " not found"));
    
    mockmvc.perform(put("/user").contentType(MediaType.APPLICATION_JSON)
         .content(objectMapper.writeValueAsString(user)))
         .andExpect(status().isNotFound())
         .andExpect(content().json("{\"message\": \"Resource Not Found\"}"))
         .andExpect(jsonPath("$.exceptionMessage").value("User with ID " + user.getUserId() + " not found"))
         .andDo(print());
    
  }
  
  @Test
  public void testUpdateUserReturningOK() throws Exception{
    User user = new User();
    user.setUserId(1);
    user.setFirstName("Ram");
    user.setLastName("Tiwari");
    user.setUserType("Platinum");
    user.setStartDate(LocalDate.of(2025, 8, 26));
    
    User updatedUser = new User();
    updatedUser.setUserId(1);
    updatedUser.setFirstName("Ram");
    updatedUser.setLastName("Tiwari");
    updatedUser.setUserType("Gold");
    updatedUser.setStartDate(LocalDate.of(2025, 8, 26));
    
    Mockito.when(userService.updateUser(user))
          .thenReturn(updatedUser);
    
    mockmvc.perform(put("/user").contentType(MediaType.APPLICATION_JSON)
         .content(objectMapper.writeValueAsString(user)))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.userType").value("Gold"))
         .andDo(print());
    
  }
  
  @Test
  public void testDeleteUserReturningResourceNotFound() throws Exception{
    int userId = 100;
    Mockito.doThrow(new ResourceNotFoundException("User with ID " + userId + " not found"))
    .when(userService).deleteUserById(userId);
    
    mockmvc.perform(delete("/user/"+userId))
         .andExpect(status().isNotFound())
         .andExpect(content().json("{\"message\": \"Resource Not Found\"}"))
         .andExpect(jsonPath("$.exceptionMessage").value("User with ID " + userId + " not found"))
         .andDo(print());
    
  }
  
  @Test
  public void testDeleteUserReturningOK() throws Exception{
    int userId = 1;

        Mockito.doNothing().when(userService).deleteUserById(userId);
        //Mockito.verify(userService, Mockito.times(1)).deleteUserById(userId);
        mockmvc.perform(delete("/user/"+userId))
         .andExpect(status().isNoContent())
         .andDo(print());
    
  }

}

Some of the points to note here are-

  1. In the method testGetUserReturningResourceNotFound(), Mockito.when().thenThrow() is used to specify the exception that has to be thrown by the mock object to test user id not found scenario.
  2. Method testGetUserReturningResourceNotFound() shows another way to extract properties from JSON other than using jsonPath.
    andExpect(content().json("{\"message\": \"Resource Not Found\"}"))
    
  3. Method deleteUserById() in the actual UserServiceImpl class doesn't return any value so unit test for that also needs to mock userservice call with that expectation.
    Mockito.doNothing().when(userService).deleteUserById(userId);
    

That's all for this topic Spring Boot Unit Test For REST API. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Boot spring-boot-starter-parent
  2. Spring Boot + Spring Security JWT Authentication Example
  3. Spring Data JPA - Spring Data Tutorial
  4. Spring Boot Observability - Distributed Tracing, Metrics
  5. Spring Bean Life Cycle

You may also like-

  1. BeanPostProcessor in Spring Framework
  2. Transaction Management in Spring
  3. Spring Web Reactive Framework - Spring WebFlux Tutorial
  4. Spring JdbcTemplate Insert, Update And Delete Example
  5. JVM Run-Time Data Areas - Java Memory Allocation
  6. Comparing Enum to String in Java
  7. Python Lambda Functions With Examples
  8. Angular ngClass Directive With Examples