This is the 23rd day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

This series code address: github.com/JoJoTec/spr…

Let’s continue with the previous section and continue testing our own wrapped WebClient using Spock

Test for readTimeout retry

For response timeouts, we need to verify that the retries are only for methods that can be retried (both GET methods and retried methods configured) and that no retries are made for methods that cannot be retried. We can see if there are retries by checking the number of calls to the load balancer instance method in spock unit tests

We implement readTimeout via ‘/delay/ second ‘at httpbin.org.

  • Test GET returns 2 seconds later than the read timeout and retry
  • The test POST returns 3 seconds later than the read timeout, and the path is in the retry path, which is also retried
  • The test POST returns 2 seconds later than the read timeout, and the path is in the retry path, so no retries will be made

The code is as follows:

@SpringBootTest( properties = [ "webclient.configs.testServiceWithCannotConnect.baseUrl=http://testServiceWithCannotConnect", "webclient.configs.testServiceWithCannotConnect.serviceName=testServiceWithCannotConnect", "webclient.configs.testService.baseUrl=http://testService", "webclient.configs.testService.serviceName=testService", "webclient.configs.testService.responseTimeout=1s", "webclient.configs.testService.retryablePaths[0]=/delay/3", "webclient.configs.testService.retryablePaths[1]=/status/4*", "spring.cloud.loadbalancer.zone=zone1", "resilience4j.retry.configs.default.maxAttempts=3", "resilience4j.circuitbreaker.configs.default.failureRateThreshold=50", "resilience4j.circuitbreaker.configs.default.slidingWindowType=TIME_BASED", "Resilience4j. Circuitbreaker. Configs. Default. SlidingWindowSize = 5", / / for retry is three times, in order to prevent the breaker on impact test, the number of proportion is set to just try one more time, Prevent trigger / / at the same time, we also need to manually clear circuit breaker when test statistics ". Resilience4j circuitbreaker. Configs. Default. MinimumNumberOfCalls = 4 ", "resilience4j.circuitbreaker.configs.default.recordExceptions=java.lang.Exception" ], classes = MockConfig ) class WebClientUnitTest extends Specification { @SpringBootApplication static class MockConfig { } @SpringBean private LoadBalancerClientFactory loadBalancerClientFactory = Mock() @Autowired private CircuitBreakerRegistry circuitBreakerRegistry @Autowired private Tracer tracer @Autowired private ServiceInstanceMetrics  serviceInstanceMetrics @Autowired private WebClientNamedContextFactory webClientNamedContextFactory Def zone1Instance1 = new DefaultServiceInstance(instanceId: "instance1", host: "www.httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) def zone1Instance2 = new DefaultServiceInstance(instanceId: "instance2", host: "www.httpbin.org", port: 8081, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) def zone1Instance3 = new DefaultServiceInstance(instanceId: "instance3", host: "httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance = Spy(); ServiceInstanceListSupplier serviceInstanceListSupplier = Spy(); / / in front of all the test method will be called the method of def setup () {/ / initialization loadBalancerClientFactoryInstance load balancer loadBalancerClientFactoryInstance.setTracer(tracer) loadBalancerClientFactoryInstance.setServiceInstanceMetrics(serviceInstanceMetrics) LoadBalancerClientFactoryInstance. SetServiceInstanceListSupplier (serviceInstanceListSupplier)} def "test for readTimeout Retry "() {given: "Set" testService instances are normal instance loadBalancerClientFactory. GetInstance (" testService ") > > loadBalancerClientFactoryInstance serviceInstanceListSupplier.get() >> Flux.just(Lists.newArrayList(zone1Instance1, zone1Instance3)) when: "Test GET return delay of 2 seconds, More than read timeout ". / / remove the breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .get().uri("/delay/2").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientRequestException e) (um participant etCause () in ReadTimeoutException) {/ / read timeout ignore} else {throw e; }} then: "every time a timeout so will try again, according to the configuration of a total of 3 times" 3 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _) when: "Test POST return delay of 3 seconds, exceed read timeout, Path to retry path at the same time, "/ / removal of circuit breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .post().uri("/delay/3").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientRequestException e) (um participant etCause () in ReadTimeoutException) {/ / read timeout ignore} else {throw e; }} then: "every time a timeout so will try again, according to the configuration of a total of 3 times" 3 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _) when: "Test POST return delay of 2 seconds, exceed read timeout, This can't retry ". / / remove the breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .post().uri("/delay/2").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientRequestException e) (um participant etCause () in ReadTimeoutException) {/ / read timeout ignore} else {throw e; }} then: "there is no retry, only one call" 1 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _)}}Copy the code

Tests for retries returned by non-2XX response codes

For a non-2xx response code that indicates a request failure, we need to test:

  • The test GET returns 500 and there will be retries
  • Test POST returns 500, no retries
  • The test POST returns 400, the request path is in the retry path, there will be retries
@SpringBootTest( properties = [ "webclient.configs.testServiceWithCannotConnect.baseUrl=http://testServiceWithCannotConnect", "webclient.configs.testServiceWithCannotConnect.serviceName=testServiceWithCannotConnect", "webclient.configs.testService.baseUrl=http://testService", "webclient.configs.testService.serviceName=testService", "webclient.configs.testService.responseTimeout=1s", "webclient.configs.testService.retryablePaths[0]=/delay/3", "webclient.configs.testService.retryablePaths[1]=/status/4*", "spring.cloud.loadbalancer.zone=zone1", "resilience4j.retry.configs.default.maxAttempts=3", "resilience4j.circuitbreaker.configs.default.failureRateThreshold=50", "resilience4j.circuitbreaker.configs.default.slidingWindowType=TIME_BASED", "Resilience4j. Circuitbreaker. Configs. Default. SlidingWindowSize = 5", / / for retry is three times, in order to prevent the breaker on impact test, the number of proportion is set to just try one more time, Prevent trigger / / at the same time, we also need to manually clear circuit breaker when test statistics ". Resilience4j circuitbreaker. Configs. Default. MinimumNumberOfCalls = 4 ", "resilience4j.circuitbreaker.configs.default.recordExceptions=java.lang.Exception" ], classes = MockConfig ) class WebClientUnitTest extends Specification { @SpringBootApplication static class MockConfig { } @SpringBean private LoadBalancerClientFactory loadBalancerClientFactory = Mock() @Autowired private CircuitBreakerRegistry circuitBreakerRegistry @Autowired private Tracer tracer @Autowired private ServiceInstanceMetrics  serviceInstanceMetrics @Autowired private WebClientNamedContextFactory webClientNamedContextFactory Def zone1Instance1 = new DefaultServiceInstance(instanceId: "instance1", host: "www.httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) def zone1Instance2 = new DefaultServiceInstance(instanceId: "instance2", host: "www.httpbin.org", port: 8081, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) def zone1Instance3 = new DefaultServiceInstance(instanceId: "instance3", host: "httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1"))) RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance = Spy(); ServiceInstanceListSupplier serviceInstanceListSupplier = Spy(); / / in front of all the test method will be called the method of def setup () {/ / initialization loadBalancerClientFactoryInstance load balancer loadBalancerClientFactoryInstance.setTracer(tracer) loadBalancerClientFactoryInstance.setServiceInstanceMetrics(serviceInstanceMetrics) LoadBalancerClientFactoryInstance. SetServiceInstanceListSupplier (serviceInstanceListSupplier)} def "test of 200 response code returns" () {  given: "Set" testService instances are normal instance loadBalancerClientFactory. GetInstance (" testService ") > > loadBalancerClientFactoryInstance serviceInstanceListSupplier.get() >> Flux.just(Lists.newArrayList(zone1Instance1, zone1Instance3)) when: "Test GET return 500" / / removal of circuit breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .get().uri("/status/500").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientResponseException e) (um participant etStatusCode () is5xxServerError ()) {/ / 5 xx ignore} else {throw e; } } then: "Every time there is no return 2 xx will try again so, according to the configuration of a total of 3 times" 3 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _) when: "Test POST return 500" / / removal of circuit breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .post().uri("/status/500").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientResponseException e) (um participant etStatusCode () is5xxServerError ()) {/ / 5 xx ignore} else {throw e; }} then: "don't POST the default retry, so will only be called a" 1 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _) when: "Test POST returns 400, In the path of the request in retry ". / / remove the breaker affect circuitBreakerRegistry getAllCircuitBreakers (). The forEach ({c - > c.r eset ()}) try { webClientNamedContextFactory.getWebClient("testService") .post().uri("/status/400").retrieve() .bodyToMono(String.class).block(); {if} the catch (WebClientResponseException e) (um participant etStatusCode () is4xxClientError ()) {/ / 4 xx ignore} else {throw e; }} then: "path in the retry path, so will retry" 3 * loadBalancerClientFactoryInstance getInstanceResponseByRoundRobin (* _)}}Copy the code

Wechat search “my programming meow” public account, a daily brush, easy to improve skills, won a variety of offers