Testing Spring Boot
Why Is Testing Important?
Testing ensures the code works correctly, makes refactoring easier, and prevents regressions. Spring Boot ships with many built-in testing tools.
Types of Tests
Unit Test → Test 1 class, mock dependencies
Integration Test → Test several components together
End-to-End Test → Test the entire application
@SpringBootTest
Loads the full application context for integration tests:
@SpringBootTest
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldCreateUser() {
UserRequestDTO request = new UserRequestDTO();
request.setName("Mike");
request.setEmail("[email protected]");
UserResponseDTO response = userService.createUser(request);
assertThat(response.getId()).isNotNull();
assertThat(response.getName()).isEqualTo("Mike");
assertThat(response.getEmail()).isEqualTo("[email protected]");
}
@Test
void shouldReturnAllUsers() {
UserRequestDTO req1 = new UserRequestDTO();
req1.setName("Mike");
req1.setEmail("[email protected]");
UserRequestDTO req2 = new UserRequestDTO();
req2.setName("John");
req2.setEmail("[email protected]");
userService.createUser(req1);
userService.createUser(req2);
List<UserResponseDTO> users = userService.getAllUsers();
assertThat(users).hasSize(2);
}
}
@WebMvcTest
Tests the controller only, without loading the full service/repository:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUser() throws Exception {
UserResponseDTO user = new UserResponseDTO();
user.setId(1L);
user.setName("Mike");
user.setEmail("[email protected]");
when(userService.getUserById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Mike"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
@Test
void shouldReturn404WhenUserNotFound() throws Exception {
when(userService.getUserById(99L))
.thenThrow(new RuntimeException("User not found"));
mockMvc.perform(get("/api/users/99"))
.andExpect(status().isNotFound());
}
}
@DataJpaTest
Tests the repository layer with an embedded database:
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveAndFindUser() {
User user = new User();
user.setName("Mike");
user.setEmail("[email protected]");
User saved = userRepository.save(user);
Optional<User> found = userRepository.findById(saved.getId());
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Mike");
}
@Test
void shouldFindByEmail() {
User user = new User();
user.setName("John");
user.setEmail("[email protected]");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("[email protected]");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("John");
}
}
@MockBean
Creates a mock bean that replaces the real bean in the Spring context:
@SpringBootTest
class OrderServiceTest {
@MockBean
private ProductRepository productRepository;
@Autowired
private OrderService orderService;
@Test
void shouldCreateOrder() {
Product product = new Product();
product.setId(1L);
product.setName("Laptop");
product.setStock(10);
when(productRepository.findById(1L)).thenReturn(Optional.of(product));
Order order = new Order();
order.setProductId(1L);
order.setQuantity(2);
Order created = orderService.createOrder(order);
assertThat(created.getId()).isNotNull();
verify(productRepository).save(any(Product.class));
}
}
AssertJ Assertions
Spring Boot bundles AssertJ. Usage examples:
@Test
void assertExamples() {
List<String> names = List.of("Mike", "John", "Anna");
// String
assertThat("Hello").isNotEmpty();
assertThat("Hello").startsWith("He").endsWith("lo");
// Collection
assertThat(names).hasSize(3);
assertThat(names).contains("Mike", "John");
assertThat(names).doesNotContain("Diana");
// Object
User user = new User();
user.setName("Mike");
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("Mike");
// Exception
assertThatThrownBy(() -> {
throw new RuntimeException("Error");
}).isInstanceOf(RuntimeException.class)
.hasMessageContaining("Error");
}
Unit Test vs Integration Test
| Aspect | Unit Test | Integration Test |
|---|---|---|
| Annotation | @WebMvcTest, @DataJpaTest |
@SpringBootTest |
| Speed | Very fast (< 1 second) | Slower (a few seconds) |
| Database | Mock / Embedded | Embedded / Real |
| Test annotation | @MockBean |
@Autowired |
| Coverage | One component | Several components |
| Spring Context | Partial | Full |
Testing Tips
- Consistent naming:
methodName_shouldExpectedBehavior_whenCondition - One aspect per test: don't test many things in one test
- Use
@BeforeEach: for the same setup - Test edge cases: null, empty, boundary values
- Use
verify(): to make sure a method was called
@Test
void createUser_shouldThrowException_whenEmailExists() {
UserRequestDTO request = new UserRequestDTO();
request.setName("Mike");
request.setEmail("[email protected]");
when(userRepository.existsByEmail("[email protected]")).thenReturn(true);
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Email already in use");
}
Testing makes your application reliable and easy to extend. In the next chapter, we'll learn about Spring Boot Actuator for production monitoring.