
Unit Test Exception Handler
- 21 installs
- 318 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit-claude-code
This is a copy of unit-test-exception-handler by giuseppe-trisciuoglio - installs and ranking accrue to the original listing.
Helps with testing & qa tasks.
About
unit-test-exception-handler is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- unit-test-exception-handler
- Testing & QA
- AI-coding skill
Unit Test Exception Handler by the numbers
- 21 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit-claude-code --skill unit-test-exception-handlerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 21 |
|---|---|
| repo stars | ★ 318 |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit-claude-code ↗ |
What it does
Helps with testing & qa tasks.
Files
Unit Testing ExceptionHandler and ControllerAdvice
Overview
This skill provides patterns for writing unit tests for Spring Boot exception handlers. It covers testing @ExceptionHandler methods in @ControllerAdvice classes using MockMvc, including HTTP status assertions, JSON response validation, field-level validation error testing, and mocking handler dependencies.
When to Use
- Writing unit tests for
@ExceptionHandlermethods - Testing
@ControllerAdviceglobal exception handling - Validating REST API error response formatting
- Mocking exceptions in controller tests
- Testing field-level validation error responses
- Asserting custom error payloads and HTTP status codes
Instructions
1. Create a test controller that throws specific exceptions to trigger each @ExceptionHandler 2. Register ControllerAdvice via setControllerAdvice() on MockMvcBuilders.standaloneSetup() 3. Assert HTTP status codes with .andExpect(status().isXxx()) 4. Verify error response fields using jsonPath("$.field") matchers 5. Test validation errors by sending invalid payloads and checking MethodArgumentNotValidException produces field-level details 6. Debug failures with .andDo(print()) — if handler not invoked, verify setControllerAdvice() is called and exception type matches
Examples
Exception Handler and Error DTO
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(404, "Not Found", ex.getMessage());
}
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(ValidationException ex) {
return new ErrorResponse(400, "Bad Request", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
return new ValidationErrorResponse(400, "Validation Failed", errors);
}
}
public record ErrorResponse(int status, String error, String message) {}
public record ValidationErrorResponse(int status, String error, Map<String, String> errors) {}Unit Test
@ExtendWith(MockitoExtension.class)
class GlobalExceptionHandlerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
GlobalExceptionHandler handler = new GlobalExceptionHandler();
mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldReturn404WhenResourceNotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.error").value("Not Found"))
.andExpect(jsonPath("$.message").value("User not found"));
}
@Test
void shouldReturn400WithFieldErrorsOnValidationFailure() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"email\":\"invalid\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.errors.name").value("must not be blank"))
.andExpect(jsonPath("$.errors.email").value("must be a valid email"));
}
}
@RestController
@RequestMapping("/api")
class TestController {
@GetMapping("/users/{id}") public User getUser(@PathVariable Long id) {
throw new ResourceNotFoundException("User not found");
}
@PostMapping("/users") public User createUser(@RequestBody @Valid User user) {
throw new ValidationException("Validation failed");
}
}Best Practices
- Test each
@ExceptionHandlermethod independently with a dedicated exception throw - Register exactly one
@ControllerAdviceinstance viasetControllerAdvice()— never skip it - Assert all fields in the error response body, not just the HTTP status
- For validation errors, verify both the field name key and the error message value
- Use
MockMvcBuilders.standaloneSetup()for isolated handler tests without full Spring context - Log assertion failures: chain
.andDo(print())to print request/response when a test fails
Common Pitfalls
- Handler not invoked: ensure
setControllerAdvice()is called on the builder - JsonPath mismatch: use
.andDo(print())to inspect actual response structure - Status is 200: missing
@ResponseStatuson the handler method - Duplicate handlers:
@Ordercontrols precedence; more specific exception types take priority - Testing handler logic instead of behavior: mock external dependencies, test only the response transformation
Constraints and Warnings
- `@ExceptionHandler` specificity: more specific exception types are matched first;
Exception.classcatches all unmatched types - `@ResponseStatus` default: without
@ResponseStatusor returningResponseEntity, HTTP status defaults to 200 - Global vs local scope:
@ExceptionHandlerin@ControllerAdviceis global; declared in a controller it is local only to that controller - Logging side effects: handlers that log should be verified with
verify(mockLogger).logXxx(...) - Localization: when using
MessageSource, test with differentLocalevalues to confirm message resolution - Security context:
AuthorizationExceptionhandlers can accessSecurityContextHolder— test that context is correctly evaluated
Exception Handler Test Examples
Handler with Dependencies (using mocks)
When your @ControllerAdvice has dependencies injected via constructor, mock them in your test:
@ControllerAdvice
public class GlobalExceptionHandler {
private final MessageService messageService;
public GlobalExceptionHandler(MessageService messageService) {
this.messageService = messageService;
}
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusiness(BusinessException ex) {
return new ErrorResponse(400, "Business Error", messageService.getMessage(ex.getErrorCode()));
}
}
class HandlerWithDepsTest {
private MockMvc mockMvc;
private MessageService messageService = mock(MessageService.class);
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.setControllerAdvice(new GlobalExceptionHandler(messageService))
.build();
}
@Test
void shouldReturnLocalizedMessage() throws Exception {
when(messageService.getMessage("USER_NOT_FOUND")).thenReturn("Utente non trovato");
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("Utente non trovato"));
verify(messageService).getMessage("USER_NOT_FOUND");
}
}Testing with Spring Security Context
When your exception handler evaluates SecurityContextHolder:
@ExceptionHandler(AuthorizationException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAuth(AuthorizationException ex) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return new ErrorResponse(403, "Forbidden", "Access denied for: " + auth.getName());
}
@Test
void shouldReturn403WhenNotAuthorized() throws Exception {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("user", null, List.of())
);
mockMvc.perform(get("/api/admin"))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.message").value("Access denied for: user"));
}Testing with MessageSource (Localization)
@ControllerAdvice
public class I18nExceptionHandler {
private final MessageSource messageSource;
public I18nExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex, Locale locale) {
String message = messageSource.getMessage(ex.getMessage(), null, locale);
return new ErrorResponse(404, "Not Found", message);
}
}
@Test
void shouldReturnLocalizedMessageItalian() throws Exception {
// Inject MessageSource into MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.setControllerAdvice(new I18nExceptionHandler(messageSource))
.setLocaleResolver(() -> Locale.ITALIAN)
.build();
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("Utente non trovato"));
}