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.
Understanding @MockBean in Spring Boot
@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.
Why Use @MockBean?
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.
A CRUD Operations Example with @MockBean
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.
The User Entity
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
}
The UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
The UserService
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();
}
}
The UserController
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();
}
}
UserServiceTest - Testing with @MockBean
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);
}
}
Explanation:
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.
UserContollerTest - Testing with @MockBean
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.
Comments
Post a Comment
Leave Comment