CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

89

Quality

89%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Overview
Quality
Evals
Security
Files

SKILL.mdplugins/developer-kit-java/skills/unit-test-exception-handler/

name:
unit-test-exception-handler
description:
Provides patterns for unit testing `@ExceptionHandler` and `@ControllerAdvice` in Spring Boot applications. Validates error response formatting, mocks exceptions, verifies HTTP status codes, tests field-level validation errors, and asserts custom error payloads. Use when writing Spring exception handler tests, REST API error tests, or mocking controller advice.
allowed-tools:
Read, Write, Bash, Glob, Grep

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 @ExceptionHandler methods
  • Testing @ControllerAdvice global 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 @ExceptionHandler method independently with a dedicated exception throw
  • Register exactly one @ControllerAdvice instance via setControllerAdvice() — 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 @ResponseStatus on the handler method
  • Duplicate handlers: @Order controls 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.class catches all unmatched types
  • @ResponseStatus default: without @ResponseStatus or returning ResponseEntity, HTTP status defaults to 200
  • Global vs local scope: @ExceptionHandler in @ControllerAdvice is 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 different Locale values to confirm message resolution
  • Security context: AuthorizationException handlers can access SecurityContextHolder — test that context is correctly evaluated

plugins

developer-kit-java

skills

unit-test-exception-handler

README.md

CHANGELOG.md

context7.json

CONTRIBUTING.md

README_CN.md

README_ES.md

README_IT.md

README.md

tessl.json

tile.json