You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Contract处理异常路径(4xx/5xx)最佳实践咨询

Spring Cloud Contract: Testing 4xx/5xx Error Paths (Consumer-Side Best Practices)

Hey there! Great news—Spring Cloud Contract absolutely supports testing 4xx/5xx error scenarios, so your POC struggles are just a matter of getting the setup right. Let me break down how to implement this, common pitfalls to avoid, and best practices for consumer-side testing.

First: Yes, It’s Supported

Don’t worry—you’re not trying to do something the framework wasn’t built for. Spring Cloud Contract lets you define explicit contracts for error responses just like you do for successful ones. The most common reason POCs fail is missing small details in the contract or consumer test setup.

Step 1: Define the Error Contract (Producer Side)

Start by writing a Groovy DSL contract that explicitly defines the error scenario. Here’s an example for a 400 Bad Request due to invalid input:

Contract.make {
    request {
        method 'POST'
        url '/api/users'
        body([
            email: 'invalid-email-format'
        ])
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 400 // Critical: Explicitly set the error status code
        body([
            errorMessage: 'Email format is invalid',
            errorCode: 'VALIDATION_ERROR'
        ])
        headers {
            contentType(applicationJson())
        }
    }
}

For a 500 Internal Server Error scenario, you might write something like:

Contract.make {
    request {
        method 'GET'
        url '/api/users/123'
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 500
        body([
            errorMessage: 'Unexpected server error',
            errorCode: 'INTERNAL_ERROR'
        ])
        headers {
            contentType(applicationJson())
        }
    }
}

Step 2: Consumer-Side Testing

Once the producer builds and publishes their stubs, you can write consumer tests that validate your error handling logic. Here are examples for both RestTemplate and WebClient:

Testing with RestTemplate

@SpringBootTest
@AutoConfigureStubRunner(ids = "com.yourorg:user-service:+:stubs:8080")
public class UserClientErrorTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    void whenPostingInvalidUser_shouldHandle400Error() {
        // Prepare invalid request
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String invalidUser = "{\"email\": \"invalid-email-format\"}";
        HttpEntity<String> request = new HttpEntity<>(invalidUser, headers);

        // Capture and validate the error
        try {
            restTemplate.postForObject("http://localhost:8080/api/users", request, UserDto.class);
            fail("Expected a 400 Bad Request exception but none was thrown");
        } catch (HttpClientErrorException.BadRequest ex) {
            assertEquals(400, ex.getStatusCode().value());
            ErrorResponse error = ex.getResponseBodyAs(ErrorResponse.class);
            assertEquals("VALIDATION_ERROR", error.getErrorCode());
            assertEquals("Email format is invalid", error.getErrorMessage());
        }
    }
}

Testing with WebClient

WebClient uses reactive error handling, so you’ll use onStatus to catch and verify errors:

@SpringBootTest
@AutoConfigureStubRunner(ids = "com.yourorg:user-service:+:stubs:8080")
public class ReactiveUserClientErrorTest {

    @Autowired
    private WebClient webClient;

    @Test
    void whenFetchingNonexistentUser_shouldHandle500Error() {
        Mono<ErrorResponse> errorMono = webClient.get()
                .uri("/api/users/123")
                .retrieve()
                .onStatus(HttpStatus::is5xxServerError, response ->
                        response.bodyToMono(ErrorResponse.class)
                                .flatMap(error -> Mono.error(new ServerException(error.getErrorCode(), error.getErrorMessage())))
                )
                .bodyToMono(UserDto.class)
                .onErrorMap(ServerException.class, ex -> ex)
                .cast(ErrorResponse.class);

        // Validate the error details
        ErrorResponse error = errorMono.block();
        assertEquals("INTERNAL_ERROR", error.getErrorCode());
        assertEquals("Unexpected server error", error.getErrorMessage());
    }
}

Consumer-Side Best Practices

  • Explicitly model error responses: Create a dedicated ErrorResponse DTO that matches the structure defined in your contracts. This ensures consistent parsing across all error scenarios.
  • Test all critical error paths: Don’t just test 400s—cover 401 (unauthorized), 404 (not found), 429 (rate limited), and 500s. Each should have its own contract and test case.
  • Validate error codes and messages: Don’t just check the status code—verify the error body content to ensure your client reacts correctly to specific failure reasons.
  • Leverage Spring’s error handling utilities: For RestTemplate, use exception subclasses like HttpClientErrorException.BadRequest; for WebClient, use onStatus to handle specific status code ranges cleanly.
  • Test fallback logic: If your client uses resilience patterns like retries or fallbacks (e.g., with Resilience4j), write tests to ensure these trigger correctly when 5xx errors occur.

Common POC Failure Reasons

If your initial tests didn’t work, check these:

  • Did you explicitly set the status field in the contract’s response block? It’s easy to forget and default to 200.
  • Are your request headers (like Content-Type) matching between the contract and your consumer test? Mismatches can cause the stub to return unexpected responses.
  • Are you correctly capturing exceptions in your consumer test? For example, RestTemplate doesn’t throw exceptions by default for 4xx/5xx if you use exchange() instead of postForObject()—make sure you’re using the right method or enabling exception handling.

内容的提问来源于stack exchange,提问作者Michael Sun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:40:14