SpringBoot RestController的Spock异常模拟测试新手求助及示例需求
Got it, let's walk through how to write Spock tests for your Spring Boot RestController, including handling exception scenarios. I'll start with a sample controller setup, then show you the Spock test cases with clear explanations.
Sample Controller & Supporting Classes
First, let's create a simple UserController that fetches a user by ID, along with a custom exception and global exception handler to format error responses (this matches your need to validate exception-based ResponseEntity outputs):
// UserController.groovy @RestController @RequestMapping("/api/users") class UserController { private final UserService userService // Constructor injection (Spring's preferred approach) UserController(UserService userService) { this.userService = userService } @GetMapping("/{id}") ResponseEntity<UserDto> getUserById(@PathVariable Long id) { UserDto user = userService.findById(id) return ResponseEntity.ok(user) } } // UserService.groovy (simplified interface for example) interface UserService { UserDto findById(Long id) } // Custom exception for missing users class UserNotFoundException extends RuntimeException { UserNotFoundException(Long id) { super("User with id $id not found") } } // DTO for error responses class ErrorResponse { int status String message ErrorResponse(int status, String message) { this.status = status this.message = message } } // Global exception handler to convert exceptions to proper ResponseEntity @RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException) ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) { ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.message) return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error) } } // User data transfer object class UserDto { Long id String name String email UserDto(Long id, String name, String email) { this.id = id this.name = name this.email = email } }
Spock Test for the Controller
Now, let's write the Spock test using @WebMvcTest (a lightweight slice test for Spring MVC components) to cover both successful and error scenarios:
// UserControllerSpec.groovy @WebMvcTest(UserController) class UserControllerSpec extends Specification { @Autowired MockMvc mockMvc // Simulates HTTP requests to our controller @MockBean UserService userService // Mocks the dependent service to control its behavior def "should return 200 OK and user details when user exists"() { given: "a valid user ID and mock user data" def userId = 1L def expectedUser = new UserDto(userId, "Alice Smith", "alice@example.com") when: "we call the GET /api/users/{id} endpoint" def response = mockMvc.perform(get("/api/users/{id}", userId) .contentType(MediaType.APPLICATION_JSON)) then: "the user service is called once with the correct ID and returns the mock user" 1 * userService.findById(userId) >> expectedUser and: "the response has 200 status and matches the expected user JSON" response.andExpect(status().isOk()) .andExpect(jsonPath('$.id').value(userId)) .andExpect(jsonPath('$.name').value("Alice Smith")) .andExpect(jsonPath('$.email').value("alice@example.com")) } def "should return 404 Not Found with error message when user does not exist"() { given: "a non-existent user ID" def invalidUserId = 99L def expectedErrorMsg = "User with id $invalidUserId not found" when: "we call the GET /api/users/{id} endpoint with the invalid ID" def response = mockMvc.perform(get("/api/users/{id}", invalidUserId) .contentType(MediaType.APPLICATION_JSON)) then: "the user service throws UserNotFoundException" 1 * userService.findById(invalidUserId) >> { throw new UserNotFoundException(invalidUserId) } and: "the response has 404 status and correct error details" response.andExpect(status().isNotFound()) .andExpect(jsonPath('$.status').value(HttpStatus.NOT_FOUND.value())) .andExpect(jsonPath('$.message').value(expectedErrorMsg)) } }
Key Explanations
Let's break down the important parts:
- @WebMvcTest: This annotation loads only the Spring MVC components related to
UserController(not the entire application context), making tests fast and focused on the web layer. - @MockBean: We mock the
UserServiceso we can control if it returns a user or throws an exception, without needing a real service implementation. - MockMvc: This tool lets us simulate HTTP requests (like
GET) and assert on the response status, headers, and body. - Spock Interaction Testing: The line
1 * userService.findById(userId) >> expectedUserverifies that the service method is called exactly once with the correct ID, and tells it to return our mock user. For the error case,>> { throw new UserNotFoundException(...) }triggers the exception we want to test. - Response Assertions: We use
andExpectto check the HTTP status code, andjsonPathto validate specific fields in the JSON response (perfect for verifying your error payload structure).
Required Dependencies
Make sure your build.gradle (Groovy DSL) includes these dependencies for testing:
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' testImplementation 'org.spockframework:spock-spring:2.3-groovy-4.0'
That's all! This setup will let you thoroughly test both happy paths and error scenarios for your RestController using Spock's expressive syntax.
内容的提问来源于stack exchange,提问作者Rohan Razdan

