@WebMvcTest and @DataJpaTest in Java

Introduction to Spring Boot Test Slices

Spring Boot provides test slicing annotations that allow you to test specific layers of your application in isolation. @WebMvcTest and @DataJpaTest are two essential annotations for testing web and data layers separately.

@WebMvcTest - Testing Web Layer

Basic @WebMvcTest Setup

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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class) // Focus only on web layer for UserController
class UserControllerWebMvcTest {
@Autowired
private MockMvc mockMvc; // Injected automatically by @WebMvcTest
@MockBean
private UserService userService; // Mock the service layer
@Test
void shouldReturnUserWhenValidId() throws Exception {
// Given
User user = new User(1L, "john.doe", "[email protected]", "John", "Doe");
when(userService.getUserById(1L)).thenReturn(user);
// When & Then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.username").value("john.doe"))
.andExpect(jsonPath("$.email").value("[email protected]"))
.andExpect(jsonPath("$.firstName").value("John"))
.andExpect(jsonPath("$.lastName").value("Doe"));
}
@Test
void shouldReturn404WhenUserNotFound() throws Exception {
// Given
when(userService.getUserById(999L)).thenThrow(new UserNotFoundException("User not found"));
// When & Then
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("User not found"));
}
}

Controller Class

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return ResponseEntity.status(201).body(createdUser);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
User updatedUser = userService.updateUser(id, user);
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
List<User> users = userService.getAllUsers(page, size);
return ResponseEntity.ok(users);
}
}

Comprehensive @WebMvcTest Examples

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
@WebMvcTest(UserController.class)
class UserControllerComprehensiveTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper; // Automatically available for JSON serialization
@MockBean
private UserService userService;
private User user1;
private User user2;
private List<User> users;
@BeforeEach
void setUp() {
user1 = new User(1L, "john.doe", "[email protected]", "John", "Doe");
user2 = new User(2L, "jane.smith", "[email protected]", "Jane", "Smith");
users = Arrays.asList(user1, user2);
}
@Nested
@DisplayName("GET /api/users")
class GetUsersTests {
@Test
@DisplayName("Should return all users with pagination")
void shouldReturnAllUsersWithPagination() throws Exception {
// Given
when(userService.getAllUsers(0, 10)).thenReturn(users);
// When & Then
mockMvc.perform(get("/api/users")
.param("page", "0")
.param("size", "10"))
.andDo(print()) // Useful for debugging
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].username").value("john.doe"))
.andExpect(jsonPath("$[1].username").value("jane.smith"));
}
@Test
@DisplayName("Should return empty list when no users")
void shouldReturnEmptyListWhenNoUsers() throws Exception {
// Given
when(userService.getAllUsers(0, 10)).thenReturn(List.of());
// When & Then
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", empty()));
}
}
@Nested
@DisplayName("POST /api/users")
class CreateUserTests {
@Test
@DisplayName("Should create user with valid data")
void shouldCreateUserWithValidData() throws Exception {
// Given
User newUser = new User(null, "new.user", "[email protected]", "New", "User");
User createdUser = new User(3L, "new.user", "[email protected]", "New", "User");
when(userService.createUser(any(User.class))).thenReturn(createdUser);
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(newUser)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.username").value("new.user"))
.andExpect(header().string("Location", "/api/users/3"));
}
@Test
@DisplayName("Should return 400 for invalid user data")
void shouldReturn400ForInvalidUserData() throws Exception {
// Given - User with invalid email
User invalidUser = new User(null, "user", "invalid-email", "First", "Last");
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(invalidUser)))
.andExpect(status().isBadRequest());
}
@Test
@DisplayName("Should return 409 for duplicate username")
void shouldReturn409ForDuplicateUsername() throws Exception {
// Given
User duplicateUser = new User(null, "john.doe", "[email protected]", "New", "User");
when(userService.createUser(any(User.class)))
.thenThrow(new DuplicateUsernameException("Username already exists"));
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(duplicateUser)))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.message").value("Username already exists"));
}
}
@Nested
@DisplayName("PUT /api/users/{id}")
class UpdateUserTests {
@Test
@DisplayName("Should update user successfully")
void shouldUpdateUserSuccessfully() throws Exception {
// Given
User updateData = new User(null, "john.updated", "[email protected]", "John", "Updated");
User updatedUser = new User(1L, "john.updated", "[email protected]", "John", "Updated");
when(userService.updateUser(eq(1L), any(User.class))).thenReturn(updatedUser);
// When & Then
mockMvc.perform(put("/api/users/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updateData)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lastName").value("Updated"));
}
}
@Nested
@DisplayName("DELETE /api/users/{id}")
class DeleteUserTests {
@Test
@DisplayName("Should delete user successfully")
void shouldDeleteUserSuccessfully() throws Exception {
// Given
doNothing().when(userService).deleteUser(1L);
// When & Then
mockMvc.perform(delete("/api/users/1"))
.andExpect(status().isNoContent());
}
@Test
@DisplayName("Should return 404 when deleting non-existent user")
void shouldReturn404WhenDeletingNonExistentUser() throws Exception {
// Given
doThrow(new UserNotFoundException("User not found"))
.when(userService).deleteUser(999L);
// When & Then
mockMvc.perform(delete("/api/users/999"))
.andExpect(status().isNotFound());
}
}
}

Testing Security with @WebMvcTest

import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
@WebMvcTest(SecuredUserController.class)
class SecuredUserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@WithMockUser(roles = "USER")
@DisplayName("Should return user for authenticated user")
void shouldReturnUserForAuthenticatedUser() throws Exception {
// Given
User user = new User(1L, "john.doe", "[email protected]", "John", "Doe");
when(userService.getUserById(1L)).thenReturn(user);
// When & Then
mockMvc.perform(get("/api/secured/users/1"))
.andExpect(status().isOk());
}
@Test
@DisplayName("Should return 401 for unauthenticated request")
void shouldReturn401ForUnauthenticatedRequest() throws Exception {
mockMvc.perform(get("/api/secured/users/1"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(roles = "USER")
@DisplayName("Should return 403 for unauthorized role")
void shouldReturn403ForUnauthorizedRole() throws Exception {
mockMvc.perform(get("/api/secured/users/1"))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
@DisplayName("Should allow access for admin role")
void shouldAllowAccessForAdminRole() throws Exception {
// Given
User user = new User(1L, "john.doe", "[email protected]", "John", "Doe");
when(userService.getUserById(1L)).thenReturn(user);
// When & Then
mockMvc.perform(get("/api/secured/users/1"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "user", roles = "USER")
@DisplayName("Should create user with CSRF token")
void shouldCreateUserWithCsrfToken() throws Exception {
// Given
User newUser = new User(null, "new.user", "[email protected]", "New", "User");
User createdUser = new User(1L, "new.user", "[email protected]", "New", "User");
when(userService.createUser(any(User.class))).thenReturn(createdUser);
// When & Then
mockMvc.perform(post("/api/secured/users")
.with(csrf()) // Include CSRF token
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(newUser)))
.andExpect(status().isCreated());
}
}

@DataJpaTest - Testing Data Layer

Basic @DataJpaTest Setup

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.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager testEntityManager; // Alternative to EntityManager for tests
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveUser() {
// Given
User user = new User("john.doe", "[email protected]", "John", "Doe");
// When
User savedUser = userRepository.save(user);
// Then
assertThat(savedUser).isNotNull();
assertThat(savedUser.getId()).isNotNull();
assertThat(savedUser.getUsername()).isEqualTo("john.doe");
assertThat(savedUser.getEmail()).isEqualTo("[email protected]");
}
@Test
void shouldFindUserById() {
// Given
User user = new User("john.doe", "[email protected]", "John", "Doe");
User savedUser = testEntityManager.persistAndFlush(user);
// When
Optional<User> foundUser = userRepository.findById(savedUser.getId());
// Then
assertThat(foundUser).isPresent();
assertThat(foundUser.get().getUsername()).isEqualTo("john.doe");
}
@Test
void shouldReturnEmptyWhenUserNotFound() {
// When
Optional<User> foundUser = userRepository.findById(999L);
// Then
assertThat(foundUser).isEmpty();
}
@Test
void shouldFindUserByUsername() {
// Given
User user = new User("john.doe", "[email protected]", "John", "Doe");
testEntityManager.persistAndFlush(user);
// When
Optional<User> foundUser = userRepository.findByUsername("john.doe");
// Then
assertThat(foundUser).isPresent();
assertThat(foundUser.get().getEmail()).isEqualTo("[email protected]");
}
@Test
void shouldFindUsersByLastName() {
// Given
User user1 = new User("john.doe", "[email protected]", "John", "Doe");
User user2 = new User("jane.doe", "[email protected]", "Jane", "Doe");
User user3 = new User("bob.smith", "[email protected]", "Bob", "Smith");
testEntityManager.persist(user1);
testEntityManager.persist(user2);
testEntityManager.persist(user3);
testEntityManager.flush();
// When
List<User> doeUsers = userRepository.findByLastName("Doe");
// Then
assertThat(doeUsers).hasSize(2);
assertThat(doeUsers).extracting(User::getUsername)
.containsExactlyInAnyOrder("john.doe", "jane.doe");
}
@Test
void shouldUpdateUser() {
// Given
User user = new User("john.doe", "[email protected]", "John", "Doe");
User savedUser = testEntityManager.persistAndFlush(user);
// When
savedUser.setEmail("[email protected]");
User updatedUser = userRepository.save(savedUser);
// Then
assertThat(updatedUser.getEmail()).isEqualTo("[email protected]");
// Verify in database
User userFromDb = testEntityManager.find(User.class, savedUser.getId());
assertThat(userFromDb.getEmail()).isEqualTo("[email protected]");
}
@Test
void shouldDeleteUser() {
// Given
User user = new User("john.doe", "[email protected]", "John", "Doe");
User savedUser = testEntityManager.persistAndFlush(user);
// When
userRepository.delete(savedUser);
testEntityManager.flush();
// Then
User deletedUser = testEntityManager.find(User.class, savedUser.getId());
assertThat(deletedUser).isNull();
}
}

Entity and Repository Classes

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(min = 3, max = 50)
@Column(unique = true, nullable = false)
private String username;
@Email
@NotBlank
@Column(unique = true, nullable = false)
private String email;
@NotBlank
@Size(max = 50)
@Column(name = "first_name", nullable = false)
private String firstName;
@NotBlank
@Size(max = 50)
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(name = "created_at")
private java.time.LocalDateTime createdAt;
// Constructors
public User() {}
public User(String username, String email, String firstName, String lastName) {
this.username = username;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
this.createdAt = java.time.LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public java.time.LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(java.time.LocalDateTime createdAt) { this.createdAt = createdAt; }
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
List<User> findByLastName(String lastName);
List<User> findByFirstNameContainingIgnoreCase(String firstName);
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
List<User> findByEmailDomain(@Param("domain") String domain);
@Query("SELECT u FROM User u WHERE u.createdAt >= :startDate AND u.createdAt <= :endDate")
List<User> findUsersCreatedBetween(@Param("startDate") java.time.LocalDateTime startDate,
@Param("endDate") java.time.LocalDateTime endDate);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
long countByLastName(String lastName);
@Query("SELECT COUNT(u) FROM User u WHERE u.lastName = :lastName")
long countUsersByLastName(@Param("lastName") String lastName);
}

Comprehensive @DataJpaTest Examples

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.dao.DataIntegrityViolationException;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
@DataJpaTest
class UserRepositoryComprehensiveTest {
@Autowired
private TestEntityManager testEntityManager;
@Autowired
private UserRepository userRepository;
private User user1;
private User user2;
@BeforeEach
void setUp() {
user1 = new User("john.doe", "[email protected]", "John", "Doe");
user2 = new User("jane.smith", "[email protected]", "Jane", "Smith");
}
@Nested
@DisplayName("CRUD Operations")
class CrudOperations {
@Test
@DisplayName("Should persist user with all fields")
void shouldPersistUserWithAllFields() {
// When
User savedUser = userRepository.save(user1);
// Then
assertThat(savedUser.getId()).isNotNull();
assertThat(savedUser.getCreatedAt()).isNotNull();
// Verify in database
User userFromDb = testEntityManager.find(User.class, savedUser.getId());
assertThat(userFromDb.getUsername()).isEqualTo("john.doe");
assertThat(userFromDb.getEmail()).isEqualTo("[email protected]");
}
@Test
@DisplayName("Should update user successfully")
void shouldUpdateUserSuccessfully() {
// Given
User savedUser = testEntityManager.persistFlushFind(user1);
// When
savedUser.setEmail("[email protected]");
User updatedUser = userRepository.save(savedUser);
// Then
assertThat(updatedUser.getEmail()).isEqualTo("[email protected]");
// Verify changes are persisted
testEntityManager.clear(); // Clear persistence context to force database read
User userFromDb = testEntityManager.find(User.class, savedUser.getId());
assertThat(userFromDb.getEmail()).isEqualTo("[email protected]");
}
@Test
@DisplayName("Should delete user successfully")
void shouldDeleteUserSuccessfully() {
// Given
User savedUser = testEntityManager.persistFlushFind(user1);
// When
userRepository.delete(savedUser);
testEntityManager.flush();
// Then
User deletedUser = testEntityManager.find(User.class, savedUser.getId());
assertThat(deletedUser).isNull();
}
}
@Nested
@DisplayName("Query Methods")
class QueryMethods {
@Test
@DisplayName("Should find user by username")
void shouldFindUserByUsername() {
// Given
testEntityManager.persistAndFlush(user1);
// When
Optional<User> foundUser = userRepository.findByUsername("john.doe");
// Then
assertThat(foundUser).isPresent();
assertThat(foundUser.get().getEmail()).isEqualTo("[email protected]");
}
@Test
@DisplayName("Should find users by last name")
void shouldFindUsersByLastName() {
// Given
User user3 = new User("bob.doe", "[email protected]", "Bob", "Doe");
testEntityManager.persist(user1);
testEntityManager.persist(user2);
testEntityManager.persist(user3);
testEntityManager.flush();
// When
List<User> doeUsers = userRepository.findByLastName("Doe");
// Then
assertThat(doeUsers).hasSize(2);
assertThat(doeUsers).extracting(User::getUsername)
.containsExactlyInAnyOrder("john.doe", "bob.doe");
}
@Test
@DisplayName("Should find users with email domain")
void shouldFindUsersWithEmailDomain() {
// Given
User user3 = new User("[email protected]", "[email protected]", "Bob", "Johnson");
testEntityManager.persist(user1);
testEntityManager.persist(user2);
testEntityManager.persist(user3);
testEntityManager.flush();
// When
List<User> gmailUsers = userRepository.findByEmailDomain("gmail.com");
// Then
assertThat(gmailUsers).hasSize(1);
assertThat(gmailUsers.get(0).getUsername()).isEqualTo("[email protected]");
}
@Test
@DisplayName("Should find users created in date range")
void shouldFindUsersCreatedInDateRange() {
// Given
User user3 = new User("old.user", "[email protected]", "Old", "User");
user3.setCreatedAt(LocalDateTime.now().minusDays(10));
testEntityManager.persist(user1);
testEntityManager.persist(user2);
testEntityManager.persist(user3);
testEntityManager.flush();
LocalDateTime startDate = LocalDateTime.now().minusDays(1);
LocalDateTime endDate = LocalDateTime.now().plusDays(1);
// When
List<User> recentUsers = userRepository.findUsersCreatedBetween(startDate, endDate);
// Then
assertThat(recentUsers).hasSize(2);
assertThat(recentUsers).extracting(User::getUsername)
.containsExactlyInAnyOrder("john.doe", "jane.smith");
}
}
@Nested
@DisplayName("Validation and Constraints")
class ValidationAndConstraints {
@Test
@DisplayName("Should enforce unique username constraint")
void shouldEnforceUniqueUsernameConstraint() {
// Given
testEntityManager.persistAndFlush(user1);
// When & Then
User duplicateUser = new User("john.doe", "[email protected]", "Different", "User");
assertThatThrownBy(() -> userRepository.save(duplicateUser))
.isInstanceOf(DataIntegrityViolationException.class);
}
@Test
@DisplayName("Should enforce unique email constraint")
void shouldEnforceUniqueEmailConstraint() {
// Given
testEntityManager.persistAndFlush(user1);
// When & Then
User duplicateEmailUser = new User("different.user", "[email protected]", "Different", "User");
assertThatThrownBy(() -> userRepository.save(duplicateEmailUser))
.isInstanceOf(DataIntegrityViolationException.class);
}
@Test
@DisplayName("Should check if username exists")
void shouldCheckIfUsernameExists() {
// Given
testEntityManager.persistAndFlush(user1);
// When & Then
assertThat(userRepository.existsByUsername("john.doe")).isTrue();
assertThat(userRepository.existsByUsername("nonexistent")).isFalse();
}
@Test
@DisplayName("Should count users by last name")
void shouldCountUsersByLastName() {
// Given
User user3 = new User("bob.doe", "[email protected]", "Bob", "Doe");
testEntityManager.persist(user1);
testEntityManager.persist(user2);
testEntityManager.persist(user3);
testEntityManager.flush();
// When & Then
assertThat(userRepository.countByLastName("Doe")).isEqualTo(2);
assertThat(userRepository.countByLastName("Smith")).isEqualTo(1);
assertThat(userRepository.countByLastName("Nonexistent")).isEqualTo(0);
}
}
@Nested
@DisplayName("Pagination and Sorting")
class PaginationAndSorting {
@BeforeEach
void setUpPaginationData() {
for (int i = 1; i <= 15; i++) {
User user = new User("user" + i, "user" + i + "@example.com", "User" + i, "Test");
testEntityManager.persist(user);
}
testEntityManager.flush();
}
@Test
@DisplayName("Should return paginated results")
void shouldReturnPaginatedResults() {
// When
org.springframework.data.domain.Page<User> page1 = userRepository.findAll(
org.springframework.data.domain.PageRequest.of(0, 5));
org.springframework.data.domain.Page<User> page2 = userRepository.findAll(
org.springframework.data.domain.PageRequest.of(1, 5));
// Then
assertThat(page1.getContent()).hasSize(5);
assertThat(page2.getContent()).hasSize(5);
assertThat(page1.getTotalElements()).isEqualTo(15);
assertThat(page1.getTotalPages()).isEqualTo(3);
}
@Test
@DisplayName("Should return sorted results")
void shouldReturnSortedResults() {
// When
List<User> users = userRepository.findAll(
org.springframework.data.domain.Sort.by("username").descending());
// Then
assertThat(users).hasSize(15);
assertThat(users.get(0).getUsername()).isEqualTo("user9");
assertThat(users.get(1).getUsername()).isEqualTo("user8");
}
}
}

Testing with Different Databases

import org.springframework.test.context.ActiveProfiles;
@DataJpaTest
@ActiveProfiles("test") // Use test profile with H2 database
class UserRepositoryH2Test {
// Tests using H2 in-memory database
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles("integration-test") // Use real database for integration tests
class UserRepositoryIntegrationTest {
// Tests using real database configuration
}

Advanced Configuration and Customization

Custom @WebMvcTest Configuration

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@WebMvcTest(
value = UserController.class,
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = CacheConfig.class)
)
@Import({SecurityConfig.class, MapperConfig.class}) // Import specific configurations
@WithUserDetails("testuser") // Default user for all tests
class UserControllerCustomConfigTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private CacheManager cacheManager; // Excluded from main app but needed for test
@Test
void shouldWorkWithCustomConfiguration() throws Exception {
// Test implementation
}
}

Custom @DataJpaTest Configuration

import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
import java.time.LocalDateTime;
import java.util.Optional;
@DataJpaTest
@Import({JpaConfig.class, AuditingConfig.class}) // Import additional configurations
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryWithAuditingTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager testEntityManager;
@Test
void shouldAuditCreatedAndModifiedDates() {
// Given
User user = new User("audit.user", "[email protected]", "Audit", "User");
// When
User savedUser = userRepository.save(user);
// Then
assertThat(savedUser.getCreatedAt()).isNotNull();
assertThat(savedUser.getCreatedBy()).isEqualTo("system");
}
}
// Configuration for auditing
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider", dateTimeProviderRef = "dateTimeProvider")
class AuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("system");
}
@Bean
public DateTimeProvider dateTimeProvider() {
return () -> Optional.of(LocalDateTime.now());
}
}

Best Practices

1. Test Organization

// Good practice: Use nested tests for better organization
@WebMvcTest(UserController.class)
class UserControllerTest {
@Nested
@DisplayName("GET /api/users")
class GetUsers {
// Tests for GET endpoint
}
@Nested
@DisplayName("POST /api/users")
class CreateUser {
// Tests for POST endpoint
}
}

2. Test Data Management

@DataJpaTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // Share test instance between methods
class UserRepositoryDataTest {
@BeforeAll
void setUpTestData() {
// Set up test data once for all tests
}
@AfterEach
void cleanUp() {
// Clean up after each test
userRepository.deleteAll();
}
}

3. Assertion Best Practices

class AssertionBestPractices {
@Test
void goodAssertions() {
// Use AssertJ for fluent assertions
assertThat(result).isNotNull();
assertThat(result.getUsername()).isEqualTo("john.doe");
assertThat(result.getEmail()).contains("@");
assertThat(usersList).hasSize(3).extracting(User::getLastName).contains("Doe");
}
@Test
void goodMockVerification() {
// Verify interactions with mocks
verify(userService, times(1)).getUserById(1L);
verify(userService, never()).deleteUser(any());
verifyNoMoreInteractions(userService);
}
}

These comprehensive examples demonstrate how to effectively use @WebMvcTest for testing web controllers in isolation and @DataJpaTest for testing JPA repositories and entities. The key benefits include faster test execution, focused testing of specific layers, and easier debugging of issues in isolated components.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper