In Spring Boot, @MockBean is a powerful annotation used for testing, particularly when dealing with external dependencies or layers that need to be isolated. This blog post will demonstrate how to effectively use @MockBean in Spring Boot for testing CRUD operations.
@MockBean is an annotation provided by Spring Boot for testing purposes. It is used to add mock objects to the Spring application context or replace existing beans with their mock versions during the execution of tests. This is especially useful in integration testing where a part of the application needs to be tested in isolation.
Isolation: It allows for testing a specific part of the application in isolation, particularly when external services are involved.
Controlled Environment: Enables the creation of a controlled testing environment by defining the expected behavior of the mock.
Integration Testing: Facilitates testing within the full Spring context without relying on external dependencies.
Let's see an example demonstrating CRUD operations in a user management system. We'll create a User entity, a repository, a service, and a controller, and then write tests using @MockBean.
import jakarta.persistence.*; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String email; // Constructors, Getters, Setters }
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User createUser(User user) { return userRepository.save(user); } public Optional<User> getUser(Long id) { return userRepository.findById(id); } public User updateUser(Long id, User userDetails) { User user = userRepository.findById(id).orElseThrow(); user.setFirstName(userDetails.getFirstName()); user.setLastName(userDetails.getLastName()); user.setEmail(userDetails.getEmail()); return userRepository.save(user); } public void deleteUser(Long id) { userRepository.deleteById(id); } public List<User> getAllUsers() { return userRepository.findAll(); } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/users") public class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { return ResponseEntity.ok(userService.createUser(user)); } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return userService.getUser(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping public ResponseEntity<List<User>> getAllUsers() { return ResponseEntity.ok(userService.getAllUsers()); } @PutMapping("/{id}") public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) { return ResponseEntity.ok(userService.updateUser(id, user)); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.ok().build(); } }
The below CRUD tests will demonstrate how to mock and assert the behavior of the UserService using @MockBean for the UserRepository.
import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @SpringBootTest public class UserServiceTest { @Autowired private UserService userService; @MockBean private UserRepository userRepository; @Test public void testCreateUser() { User mockUser = new User(1L, "John", "Doe", "john@example.com"); Mockito.when(userRepository.save(any(User.class))).thenReturn(mockUser); User result = userService.createUser(new User()); assertEquals(mockUser, result); } @Test public void testGetUserById() { Long userId = 1L; User mockUser = new User(userId, "John", "Doe", "john@example.com"); Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); Optional<User> result = userService.getUserById(userId); assertTrue(result.isPresent()); assertEquals(mockUser, result.get()); } @Test public void testUpdateUser() { Long userId = 1L; User existingUser = new User(userId, "John", "Doe", "john@example.com"); User updatedInfo = new User(userId, "Jane", "Doe", "jane@example.com"); Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser)); Mockito.when(userRepository.save(any(User.class))).thenReturn(updatedInfo); User result = userService.updateUser(userId, updatedInfo); assertEquals(updatedInfo.getFirstName(), result.getFirstName()); assertEquals(updatedInfo.getLastName(), result.getLastName()); assertEquals(updatedInfo.getEmail(), result.getEmail()); } @Test public void testDeleteUser() { Long userId = 1L; Mockito.doNothing().when(userRepository).deleteById(eq(userId)); userService.deleteUser(userId); Mockito.verify(userRepository, Mockito.times(1)).deleteById(userId); } }
testGetUserById: This test verifies that the getUserById method in UserService correctly retrieves a user by ID using the mocked UserRepository.
testUpdateUser: This test checks if the updateUser method in UserService correctly updates a user's information and returns the updated user.
testDeleteUser: This test ensures that the deleteUser method in UserService correctly invokes the deleteById method on the UserRepository.
By mocking the UserRepository and defining its behavior, we're able to isolate and test the UserService methods effectively, ensuring that they perform as expected without the need for actual database interaction. This approach is vital for writing reliable and maintainable unit tests in Spring Boot applications.
To test the UserController in a Spring Boot application, we'll use MockMvc which simulates HTTP requests to the controller and allows us to assert the responses. We will mock the UserService to isolate the controller layer. Let's write test cases for each CRUD operation in the UserControllerTest class.
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @ExtendWith(SpringExtension.class) @WebMvcTest(UserController.class) public class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; private User sampleUser; @BeforeEach void setUp() { sampleUser = new User(1L, "John", "Doe", "john@example.com"); } @Test public void createUserTest() throws Exception { Mockito.when(userService.createUser(Mockito.any(User.class))).thenReturn(sampleUser); mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"firstName\":\"John\",\"lastName\":\"Doe\",\"email\":\"john@example.com\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.firstName").value("John")) .andExpect(jsonPath("$.lastName").value("Doe")); } @Test public void getUserByIdTest() throws Exception { Mockito.when(userService.getUserById(sampleUser.getId())).thenReturn(Optional.of(sampleUser)); mockMvc.perform(get("/users/" + sampleUser.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.firstName").value("John")) .andExpect(jsonPath("$.email").value("john@example.com")); } @Test public void updateUserTest() throws Exception { User updatedUser = new User(sampleUser.getId(), "Jane", "Doe", "jane@example.com"); Mockito.when(userService.updateUser(Mockito.eq(sampleUser.getId()), Mockito.any(User.class))).thenReturn(updatedUser); mockMvc.perform(put("/users/" + sampleUser.getId()) .contentType(MediaType.APPLICATION_JSON) .content("{\"firstName\":\"Jane\",\"lastName\":\"Doe\",\"email\":\"jane@example.com\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.firstName").value("Jane")) .andExpect(jsonPath("$.email").value("jane@example.com")); } @Test public void deleteUserTest() throws Exception { Mockito.doNothing().when(userService).deleteUser(sampleUser.getId()); mockMvc.perform(delete("/users/" + sampleUser.getId())) .andExpect(status().isOk()); } }
In these tests, MockMvc is used to simulate HTTP requests to the UserController, and UserService is mocked using @MockBean to ensure the tests are isolated to the controller layer. This approach allows us to verify that the UserController handles requests correctly and returns the appropriate responses for each CRUD operation.