Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build Spring Boot 3.x REST APIs, microservices, and reactive WebFlux apps with Spring Security 6 and JPA.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/testing.md
1# Testing - Spring Boot Test23## Unit Testing with JUnit 545```java6@ExtendWith(MockitoExtension.class)7class UserServiceTest {89@Mock10private UserRepository userRepository;1112@Mock13private PasswordEncoder passwordEncoder;1415@InjectMocks16private UserService userService;1718@Test19@DisplayName("Should create user successfully")20void shouldCreateUser() {21// Given22UserCreateRequest request = new UserCreateRequest(23"[email protected]",24"Password123",25"testuser",262527);2829User user = User.builder()30.id(1L)31.email(request.email())32.username(request.username())33.build();3435when(userRepository.existsByEmail(request.email())).thenReturn(false);36when(passwordEncoder.encode(request.password())).thenReturn("encodedPassword");37when(userRepository.save(any(User.class))).thenReturn(user);3839// When40UserResponse response = userService.create(request);4142// Then43assertThat(response).isNotNull();44assertThat(response.email()).isEqualTo(request.email());4546verify(userRepository).existsByEmail(request.email());47verify(passwordEncoder).encode(request.password());48verify(userRepository).save(any(User.class));49}5051@Test52@DisplayName("Should throw exception when email already exists")53void shouldThrowExceptionWhenEmailExists() {54// Given55UserCreateRequest request = new UserCreateRequest(56"[email protected]",57"Password123",58"testuser",592560);6162when(userRepository.existsByEmail(request.email())).thenReturn(true);6364// When & Then65assertThatThrownBy(() -> userService.create(request))66.isInstanceOf(DuplicateResourceException.class)67.hasMessageContaining("Email already registered");6869verify(userRepository, never()).save(any(User.class));70}71}72```7374## Integration Testing with @SpringBootTest7576```java77@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)78@ActiveProfiles("test")79@TestMethodOrder(MethodOrderer.OrderAnnotation.class)80class UserIntegrationTest {8182@Autowired83private TestRestTemplate restTemplate;8485@Autowired86private UserRepository userRepository;8788@BeforeEach89void setUp() {90userRepository.deleteAll();91}9293@Test94@Order(1)95@DisplayName("Should create user via API")96void shouldCreateUserViaApi() {97// Given98UserCreateRequest request = new UserCreateRequest(99"[email protected]",100"Password123",101"testuser",10225103);104105// When106ResponseEntity<UserResponse> response = restTemplate.postForEntity(107"/api/v1/users",108request,109UserResponse.class110);111112// Then113assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);114assertThat(response.getBody()).isNotNull();115assertThat(response.getBody().email()).isEqualTo(request.email());116assertThat(response.getHeaders().getLocation()).isNotNull();117}118119@Test120@Order(2)121@DisplayName("Should return validation error for invalid request")122void shouldReturnValidationError() {123// Given124UserCreateRequest request = new UserCreateRequest(125"invalid-email",126"short",127"u",12815129);130131// When132ResponseEntity<ValidationErrorResponse> response = restTemplate.postForEntity(133"/api/v1/users",134request,135ValidationErrorResponse.class136);137138// Then139assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);140assertThat(response.getBody()).isNotNull();141assertThat(response.getBody().errors()).isNotEmpty();142}143}144```145146## Web Layer Testing with MockMvc147148```java149@WebMvcTest(UserController.class)150@Import(SecurityConfig.class)151class UserControllerTest {152153@Autowired154private MockMvc mockMvc;155156@MockBean157private UserService userService;158159@Autowired160private ObjectMapper objectMapper;161162@Test163@WithMockUser(roles = "ADMIN")164@DisplayName("Should get all users")165void shouldGetAllUsers() throws Exception {166// Given167Page<UserResponse> users = new PageImpl<>(List.of(168new UserResponse(1L, "[email protected]", "user1", 25, true, null, null),169new UserResponse(2L, "[email protected]", "user2", 30, true, null, null)170));171172when(userService.findAll(any(Pageable.class))).thenReturn(users);173174// When & Then175mockMvc.perform(get("/api/v1/users")176.contentType(MediaType.APPLICATION_JSON))177.andExpect(status().isOk())178.andExpect(jsonPath("$.content").isArray())179.andExpect(jsonPath("$.content.length()").value(2))180.andExpect(jsonPath("$.content[0].email").value("[email protected]"))181.andDo(print());182}183184@Test185@WithMockUser(roles = "ADMIN")186@DisplayName("Should create user")187void shouldCreateUser() throws Exception {188// Given189UserCreateRequest request = new UserCreateRequest(190"[email protected]",191"Password123",192"testuser",19325194);195196UserResponse response = new UserResponse(1971L,198request.email(),199request.username(),200request.age(),201true,202LocalDateTime.now(),203LocalDateTime.now()204);205206when(userService.create(any(UserCreateRequest.class))).thenReturn(response);207208// When & Then209mockMvc.perform(post("/api/v1/users")210.contentType(MediaType.APPLICATION_JSON)211.content(objectMapper.writeValueAsString(request)))212.andExpect(status().isCreated())213.andExpect(header().exists("Location"))214.andExpect(jsonPath("$.email").value(request.email()))215.andExpect(jsonPath("$.username").value(request.username()))216.andDo(print());217}218219@Test220@WithMockUser(roles = "USER")221@DisplayName("Should return 403 for non-admin user")222void shouldReturn403ForNonAdmin() throws Exception {223mockMvc.perform(get("/api/v1/users")224.contentType(MediaType.APPLICATION_JSON))225.andExpect(status().isForbidden());226}227}228```229230## Data JPA Testing231232```java233@DataJpaTest234@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)235@ActiveProfiles("test")236class UserRepositoryTest {237238@Autowired239private UserRepository userRepository;240241@Autowired242private TestEntityManager entityManager;243244@Test245@DisplayName("Should find user by email")246void shouldFindUserByEmail() {247// Given248User user = User.builder()249.email("[email protected]")250.password("password")251.username("testuser")252.active(true)253.build();254255entityManager.persistAndFlush(user);256257// When258Optional<User> found = userRepository.findByEmail("[email protected]");259260// Then261assertThat(found).isPresent();262assertThat(found.get().getEmail()).isEqualTo("[email protected]");263}264265@Test266@DisplayName("Should check if email exists")267void shouldCheckIfEmailExists() {268// Given269User user = User.builder()270.email("[email protected]")271.password("password")272.username("testuser")273.active(true)274.build();275276entityManager.persistAndFlush(user);277278// When279boolean exists = userRepository.existsByEmail("[email protected]");280281// Then282assertThat(exists).isTrue();283}284285@Test286@DisplayName("Should fetch user with roles")287void shouldFetchUserWithRoles() {288// Given289Role adminRole = Role.builder().name("ADMIN").build();290entityManager.persist(adminRole);291292User user = User.builder()293.email("[email protected]")294.password("password")295.username("admin")296.active(true)297.roles(Set.of(adminRole))298.build();299300entityManager.persistAndFlush(user);301entityManager.clear();302303// When304Optional<User> found = userRepository.findByEmailWithRoles("[email protected]");305306// Then307assertThat(found).isPresent();308assertThat(found.get().getRoles()).hasSize(1);309assertThat(found.get().getRoles()).extracting(Role::getName).contains("ADMIN");310}311}312```313314## Testcontainers for Database315316```java317@SpringBootTest318@Testcontainers319@ActiveProfiles("test")320class UserServiceIntegrationTest {321322@Container323static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")324.withDatabaseName("testdb")325.withUsername("test")326.withPassword("test");327328@DynamicPropertySource329static void configureProperties(DynamicPropertyRegistry registry) {330registry.add("spring.datasource.url", postgres::getJdbcUrl);331registry.add("spring.datasource.username", postgres::getUsername);332registry.add("spring.datasource.password", postgres::getPassword);333}334335@Autowired336private UserService userService;337338@Autowired339private UserRepository userRepository;340341@BeforeEach342void setUp() {343userRepository.deleteAll();344}345346@Test347@DisplayName("Should create and find user in real database")348void shouldCreateAndFindUser() {349// Given350UserCreateRequest request = new UserCreateRequest(351"[email protected]",352"Password123",353"testuser",35425355);356357// When358UserResponse created = userService.create(request);359UserResponse found = userService.findById(created.id());360361// Then362assertThat(found).isNotNull();363assertThat(found.email()).isEqualTo(request.email());364}365}366```367368## Testing Reactive Endpoints with WebTestClient369370```java371@WebFluxTest(UserReactiveController.class)372class UserReactiveControllerTest {373374@Autowired375private WebTestClient webTestClient;376377@MockBean378private UserReactiveService userService;379380@Test381@DisplayName("Should get user reactively")382void shouldGetUserReactively() {383// Given384UserResponse user = new UserResponse(3851L,386"[email protected]",387"testuser",38825,389true,390LocalDateTime.now(),391LocalDateTime.now()392);393394when(userService.findById(1L)).thenReturn(Mono.just(user));395396// When & Then397webTestClient.get()398.uri("/api/v1/users/{id}", 1L)399.accept(MediaType.APPLICATION_JSON)400.exchange()401.expectStatus().isOk()402.expectBody(UserResponse.class)403.value(response -> {404assertThat(response.id()).isEqualTo(1L);405assertThat(response.email()).isEqualTo("[email protected]");406});407}408409@Test410@DisplayName("Should create user reactively")411void shouldCreateUserReactively() {412// Given413UserCreateRequest request = new UserCreateRequest(414"[email protected]",415"Password123",416"testuser",41725418);419420UserResponse response = new UserResponse(4211L,422request.email(),423request.username(),424request.age(),425true,426LocalDateTime.now(),427LocalDateTime.now()428);429430when(userService.create(any(UserCreateRequest.class))).thenReturn(Mono.just(response));431432// When & Then433webTestClient.post()434.uri("/api/v1/users")435.contentType(MediaType.APPLICATION_JSON)436.body(Mono.just(request), UserCreateRequest.class)437.exchange()438.expectStatus().isCreated()439.expectHeader().exists("Location")440.expectBody(UserResponse.class)441.value(user -> {442assertThat(user.email()).isEqualTo(request.email());443});444}445}446```447448## Testing Configuration449450```java451// application-test.yml452spring:453datasource:454url: jdbc:h2:mem:testdb455driver-class-name: org.h2.Driver456jpa:457hibernate:458ddl-auto: create-drop459show-sql: true460properties:461hibernate:462format_sql: true463security:464user:465name: test466password: test467468logging:469level:470org.hibernate.SQL: DEBUG471org.hibernate.type.descriptor.sql.BasicBinder: TRACE472473// Test Configuration Class474@TestConfiguration475public class TestConfig {476477@Bean478@Primary479public PasswordEncoder passwordEncoder() {480return new BCryptPasswordEncoder(4); // Faster for tests481}482483@Bean484public Clock fixedClock() {485return Clock.fixed(486Instant.parse("2024-01-01T00:00:00Z"),487ZoneId.of("UTC")488);489}490}491```492493## Test Fixtures with @DataJpaTest494495```java496@Component497public class TestDataFactory {498499public static User createUser(String email, String username) {500return User.builder()501.email(email)502.password("encodedPassword")503.username(username)504.active(true)505.createdAt(LocalDateTime.now())506.updatedAt(LocalDateTime.now())507.build();508}509510public static UserCreateRequest createUserRequest() {511return new UserCreateRequest(512"[email protected]",513"Password123",514"testuser",51525516);517}518}519```520521## Quick Reference522523| Annotation | Purpose |524|------------|---------|525| `@SpringBootTest` | Full application context integration test |526| `@WebMvcTest` | Test MVC controllers with mocked services |527| `@WebFluxTest` | Test reactive controllers |528| `@DataJpaTest` | Test JPA repositories with in-memory database |529| `@MockBean` | Add mock bean to Spring context |530| `@WithMockUser` | Mock authenticated user for security tests |531| `@Testcontainers` | Enable Testcontainers support |532| `@ActiveProfiles` | Activate specific Spring profiles for test |533534## Testing Best Practices535536- Write tests following AAA pattern (Arrange, Act, Assert)537- Use descriptive test names with @DisplayName538- Mock external dependencies, use real DB with Testcontainers539- Achieve 85%+ code coverage540- Test happy path and edge cases541- Use @Transactional for test data cleanup542- Separate unit tests from integration tests543- Use parameterized tests for multiple scenarios544- Test security rules and validation545- Keep tests fast and independent546