Testing Spring Boot applications with JUnit and Mockito is a good practice to ensure the correctness and reliability of your application. Below, I’ll present a brief overview of how to write such test cases with explanations and examples.
1. JUnit Basics in Spring Boot
Spring Boot provides out-of-the-box support for JUnit through the spring-boot-starter-test dependency, which includes:
- JUnit 5 (Jupiter) for writing test cases.
- Mockito for mocking dependencies.
- Additional libraries like Hamcrest and AssertJ for assertions.
Add the dependency in your pom.xml (if it’s not already present):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. Structural Overview
Testing can be divided into:
- Unit Tests: Independent and isolated tests of individual classes, written using JUnit + Mockito.
- Integration Tests: Testing multiple layers/classes, typically with Spring’s
@SpringBootTest.
3. Writing Unit Tests with JUnit and Mockito
Below is an example of unit testing a Service class.
Example Scenario:
We have a class that depends on UserRepository. UserService
UserService.java:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
}
}
UserRepository.java:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Corresponding Test Case
@ExtendWith(MockitoExtension.class) // Enables Mockito in JUnit 5
class UserServiceTest {
@Mock // Creates a mock instance of UserRepository
private UserRepository userRepository;
@InjectMocks // Creates UserService and injects the mock UserRepository
private UserService userService;
@Test
void testFindUserById_UserExists() {
// Arrange
Long userId = 1L;
User mockUser = new User(userId, "John Doe", "[email protected]");
// Mock the behavior of userRepository
Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser));
// Act
User result = userService.findUserById(userId);
// Assert
assertNotNull(result);
assertEquals(mockUser.getName(), result.getName());
Mockito.verify(userRepository).findById(userId); // Verify method call
}
@Test
void testFindUserById_UserNotFound() {
// Arrange
Long userId = 1L;
// Mock the behavior of userRepository
Mockito.when(userRepository.findById(userId)).thenReturn(Optional.empty());
// Act & Assert
Exception exception = assertThrows(RuntimeException.class, () -> userService.findUserById(userId));
assertEquals("User not found", exception.getMessage());
}
}
4. Integration Tests with @SpringBootTest
Integration tests are used to test the entire Spring application context.
Testing UserController
UserController.java:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.findUserById(id));
}
}
Test Case for UserController
Use @SpringBootTest with for integration-like testing. MockMvc
@SpringBootTest
@AutoConfigureMockMvc // Configures MockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean // Mock a bean in the Spring context
private UserService userService;
@Test
void testGetUserById() throws Exception {
// Arrange
Long userId = 1L;
User mockUser = new User(userId, "John Doe", "[email protected]");
Mockito.when(userService.findUserById(userId)).thenReturn(mockUser);
// Act and Assert
mockMvc.perform(get("/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("[email protected]"));
Mockito.verify(userService).findUserById(userId);
}
}
5. Tips for Effective Testing
- Mock dependencies: Mock all external dependencies to isolate the unit being tested.
- Use Assertions effectively: Leverage libraries like AssertJ or Hamcrest for expressive assertions.
- Test exceptions: Always test boundary cases and exceptions.
- Verify mock behavior: Use
Mockito.verify()to ensure mocked methods were invoked correctly. - Spring utilities:
@MockBeanis useful for overriding beans in the application context for integration testing.
Summary
Spring Boot testing with JUnit and Mockito allows you to:
- Write isolated unit tests for business logic.
- Write integration tests to validate Spring components working together.
