Spring Testing
Testing Annotations Overview
| Annotation | Description | Use Case |
|---|---|---|
@SpringBootTest | Loads the full application context | Integration tests |
@WebMvcTest | Loads only the web layer (controllers) | Controller unit tests |
@DataJpaTest | Loads only JPA components, uses in-memory DB | Repository tests |
@MockBean | Creates a Mockito mock and adds it to the Spring context | Mocking dependencies |
@ExtendWith(SpringExtension.class) | Integrates Spring with JUnit 5 | All Spring tests |
package com.example.controller;
import com.example.model.User;
import com.example.service.UserService;
import org.junit.jupiter.api.Test;
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.web.servlet.MockMvc;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// @WebMvcTest loads only the web layer — fast, no DB needed
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc; // Simulates HTTP requests without starting a server
@MockBean
private UserService userService; // Replaces real UserService with a Mockito mock
@Test
void getAllUsers_shouldReturn200WithUserList() throws Exception {
// Arrange: define mock behavior
when(userService.findAll()).thenReturn(
List.of(new User(1L, "Alice", "alice@example.com"),
new User(2L, "Bob", "bob@example.com"))
);
// Act & Assert: perform GET and verify response
mockMvc.perform(get("/api/users")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(2))
.andExpect(jsonPath("$[0].name").value("Alice"))
.andExpect(jsonPath("$[1].email").value("bob@example.com"));
verify(userService, times(1)).findAll();
}
@Test
void getUserById_whenNotFound_shouldReturn404() throws Exception {
when(userService.findById(99L)).thenReturn(Optional.empty());
mockMvc.perform(get("/api/users/99"))
.andExpect(status().isNotFound());
}
@Test
void createUser_withValidData_shouldReturn201() throws Exception {
User newUser = new User(null, "Charlie", "charlie@example.com");
User saved = new User(3L, "Charlie", "charlie@example.com");
when(userService.save(any(User.class))).thenReturn(saved);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Charlie\",\"email\":\"charlie@example.com\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.name").value("Charlie"));
}
}
package com.example.controller;
import com.example.model.User;
import com.example.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User saved = userService.save(user);
return ResponseEntity.status(201).body(saved);
}
}
Testing Services with Mockito
package com.example.service;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
// Pure Mockito test — no Spring context loaded (very fast)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository; // Mockito mock
@InjectMocks
private UserService userService; // Injects the mock into UserService
@Test
void findById_whenUserExists_shouldReturnUser() {
User user = new User(1L, "Alice", "alice@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertThat(result).isPresent();
assertThat(result.get().getName()).isEqualTo("Alice");
verify(userRepository).findById(1L);
}
@Test
void save_withDuplicateEmail_shouldThrowException() {
User user = new User(null, "Bob", "bob@example.com");
when(userRepository.existsByEmail("bob@example.com")).thenReturn(true);
assertThatThrownBy(() -> userService.save(user))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Email already exists");
verify(userRepository, never()).save(any());
}
}
@DataJpaTest for Repository Testing
package com.example.repository;
import com.example.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
// @DataJpaTest: loads JPA layer only, uses H2 in-memory DB by default
// Each test runs in a transaction that is rolled back after the test
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager; // Helper for persisting test data
@Autowired
private UserRepository userRepository;
@Test
void findByEmail_whenExists_shouldReturnUser() {
// Arrange: persist a user directly via EntityManager
User user = new User(null, "Alice", "alice@example.com");
entityManager.persistAndFlush(user);
// Act
Optional<User> found = userRepository.findByEmail("alice@example.com");
// Assert
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
@Test
void existsByEmail_whenNotExists_shouldReturnFalse() {
boolean exists = userRepository.existsByEmail("nobody@example.com");
assertThat(exists).isFalse();
}
@Test
void save_shouldPersistAndGenerateId() {
User user = new User(null, "Bob", "bob@example.com");
User saved = userRepository.save(user);
assertThat(saved.getId()).isNotNull();
assertThat(saved.getId()).isGreaterThan(0);
}
}
package com.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.*;
import static org.assertj.core.api.Assertions.*;
// @SpringBootTest: loads full context, starts embedded server on random port
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserIntegrationTest {
@LocalServerPort
private int port; // Injected random port
@Autowired
private TestRestTemplate restTemplate; // HTTP client for integration tests
@Test
void getUsers_shouldReturnOk() {
ResponseEntity<String> response = restTemplate.getForEntity(
"http://localhost:" + port + "/api/users", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
void createUser_shouldReturn201() {
String body = "{\"name\":\"Test\",\"email\":\"test@example.com\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<String> response = restTemplate.postForEntity(
"http://localhost:" + port + "/api/users",
new HttpEntity<>(body, headers), String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
}
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.