Write Spring Boot Tests
Analyze $ARGUMENTS and write comprehensive unit or integration tests.
Steps
- Read the source class under test (controller, service, repository, etc.).
- Read the existing test class if one exists.
- Read the project's documentation for testing conventions, test runner (JUnit 5), and mocking approach (Mockito, MockMvc, etc.).
- Identify what needs to be tested based on the type of Spring artifact.
- Write or update the test class, following best practices for isolation, coverage, and clarity.
What to Test by Type
Controllers
- HTTP endpoint mappings (GET, POST, PUT, DELETE)
- Request/response status codes
- Input validation (valid/invalid payloads)
- Response body structure and content
- Service method calls (mock services, verify interactions)
- Exception handling and error responses
- Security (authentication/authorization, if applicable)
Services
- Public methods (business logic, edge cases)
- Interactions with repositories (mock repositories, verify calls)
- Exception handling (expected and unexpected cases)
- State transitions (if service manages state)
- Data transformation and mapping
Repositories
- CRUD operations (save, find, update, delete)
- Custom query methods
- Query results for typical and edge cases
- Transactional behavior (if relevant)
Configuration/Properties
- Property binding with
@ConfigurationPropertiesor@Value - Profile-specific configuration
- Default values and overrides
Exception Handlers
- Global exception mapping (e.g.,
@ControllerAdvice) - Error response structure and status codes
- Handling of custom and standard exceptions
Testing Patterns
Controller Test Setup (Spring MVC - MockMvc)
java
// Standard REST controller test with MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUserWhenValidId() {
UserDto user = new UserDto(1L, "Test");
when(userService.getUserById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1L));
}
@Test
void shouldUploadFile() {
MockMultipartFile file = new MockMultipartFile(
"file", "test.txt", "text/plain", "Hello World".getBytes()
);
mockMvc.perform(multipart("/api/files/upload").file(file))
.andExpect(status().isOk());
}
}Controller Test Setup (Spring WebFlux - WebTestClient)
java
// Reactive REST controller test with WebTestClient
import org.springframework.test.web.reactive.server.WebTestClient;
@WebFluxTest(ReactiveUserController.class)
class ReactiveUserControllerTest {
@Autowired
private WebTestClient webTestClient;
@MockBean
private ReactiveUserService userService;
@Test
void shouldReturnUserMono() {
UserDto user = new UserDto("1", "Reactive");
when(userService.getUserById("1")).thenReturn(Mono.just(user));
webTestClient.get().uri("/api/reactive/users/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.id").isEqualTo("1");
}
@Test
void shouldReturnUserFlux() {
UserDto user1 = new UserDto("1", "A");
UserDto user2 = new UserDto("2", "B");
when(userService.getAllUsers()).thenReturn(Flux.just(user1, user2));
webTestClient.get().uri("/api/reactive/users")
.exchange()
.expectStatus().isOk()
.expectBodyList(UserDto.class)
.hasSize(2)
.contains(user1, user2);
}
@Test
void shouldUploadFileReactive() {
webTestClient.post()
.uri("/api/reactive/files/upload")
.bodyValue(new FileSystemResource("src/test/resources/test.txt"))
.exchange()
.expectStatus().isOk();
}
}Service Test Setup (Mockito)
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldReturnUserDtoWhenUserExists() {
User user = new User(1L, "Test");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
UserDto dto = userService.getUserById(1L);
assertEquals(1L, dto.getId());
}
}Repository Test Setup (DataJpaTest)
java
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveAndFindUser() {
User user = new User(null, "Test");
user = userRepository.save(user);
Optional<User> found = userRepository.findById(user.getId());
assertTrue(found.isPresent());
}
}Configuration Properties Test
java
@SpringBootTest(properties = "app.name=TestApp")
class AppPropertiesTest {
@Autowired
private AppProperties appProperties;
@Test
void shouldBindProperty() {
assertEquals("TestApp", appProperties.getName());
}
}Exception Handler Test
java
@WebFluxTest
class GlobalExceptionHandlerTest {
@Autowired
private WebTestClient webTestClient;
@MockBean
private UserService userService;
@Test
void shouldReturnNotFoundForMissingUser() {
when(userService.getUserById(anyLong())).thenThrow(new ResourceNotFoundException("Not found"));
webTestClient.get().uri("/api/users/99")
.exchange()
.expectStatus().isNotFound()
.expectBody()
.jsonPath("$.error").isEqualTo("NOT_FOUND");
}
}Rules
- Place test classes in the same package structure under
src/test/javaas the source class. - Name test classes after the class under test, suffixed with
Test(e.g.,UserServiceTest). - Mock all dependencies except the class under test.
- Use expressive assertions (AssertJ, Hamcrest, or JUnit assertions).
- Test both typical and edge cases (nulls, errors, empty results).
- For controllers, use WebTestClient for HTTP request/response testing.
- For services, use Mockito for mocking dependencies.
- For repositories, use
@DataR2dbcTestor reactive repository test slices (e.g., ReactiveMongo + Testcontainers). - For configuration, use
@SpringBootTestwith test properties. - Avoid unnecessary use of
@SpringBootTestfor unit tests; prefer it for integration tests. - Use descriptive test method names:
shouldReturnUserWhenValidId. - Clean up resources after tests if needed.
- Run all tests in CI pipelines and fail builds on test failures.
Output
After writing tests, summarize:
- Test class location
- Number of test cases written
- What behaviors are covered
- Any edge cases or scenarios that are difficult to test automatically