This is the 18th 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…

In the previous section, we verified the correctness of thread isolation through unit tests. In this section, we will verify the correctness of our circuit breaker, mainly including:

  1. Verify that the configuration loads correctly: that is, we configure in Spring (e.gapplication.ymlThe Resilience4j configuration added in) is loaded and applied correctly.
  2. Verify that the circuit breaker is turned on based on services and methods, that is, a method of a microservice is turned on without affecting other method calls to that microservice

Verify that the configuration loads correctly

Similar to the validation retry before, we can define a different FeignClient and then check the circuit breaker configuration loaded by Resilience4J to verify that the thread isolation configuration is loaded correctly.

And, unlike the retry configuration, we know from the previous series of source code analyses that Spring-Cloud-OpenFeign’s FeignClient is lazily loaded. So the circuit breaker we implement is lazily loaded and needs to be called before it is initialized. So here we need to validate the circuit breaker configuration after making the call.

First, define two FeignClients. The microservices are testService1 and testService2, and the contextId is testService1Client and testService2Client

@FeignClient(name = "testService1", contextId = "testService1Client") public interface TestService1Client { @GetMapping("/anything") HttpBinAnythingResponse  anything(); } @FeignClient(name = "testService2", contextId = "testService2Client") public interface TestService2Client { @GetMapping("/anything") HttpBinAnythingResponse  anything(); }Copy the code

Then, we add the Spring configuration, add an instance to each of the two microservices, and write the unit test classes using SpringExtension:

//SpringExtension also includes Mockito related Extension, So annotations like @mock also work @extendWith (springExtension.class) @springbooTtest (properties = {// The default request retry count is 3 "resilience4j.retry.configs.default.maxAttempts=3", / / all method request retry count testService2Client inside a 2 "resilience4j. Retry. Configs. TestService2Client. MaxAttempts = 2", / / the default circuit breaker configuration "resilience4j. Circuitbreaker. Configs. Default. SlidingWindowSize = 5", "resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=2", / / testService2Client circuit breaker configuration "resilience4j. Circuitbreaker. Configs. TestService2Client. FailureRateThreshold = 30", "resilience4j.circuitbreaker.configs.testService2Client.minimumNumberOfCalls=10", }) @Log4j2 public class OpenFeignClientTest { @SpringBootApplication @Configuration public static class App { @Bean Public DiscoveryClient DiscoveryClient () {// Simulate two service instances. ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class); ServiceInstance service2Instance2 = Mockito.spy(ServiceInstance.class); Map<String, String> zone1 = Map.ofEntries( Map.entry("zone", "zone1") ); when(service1Instance1.getMetadata()).thenReturn(zone1); when(service1Instance1.getInstanceId()).thenReturn("service1Instance1"); when(service1Instance1.getHost()).thenReturn("www.httpbin.org"); when(service1Instance1.getPort()).thenReturn(80); when(service2Instance2.getInstanceId()).thenReturn("service1Instance2"); when(service2Instance2.getHost()).thenReturn("httpbin.org"); when(service2Instance2.getPort()).thenReturn(80); DiscoveryClient spy = Mockito.spy(DiscoveryClient.class); Mockito.when(spy.getInstances("testService1")) .thenReturn(List.of(service1Instance1)); Mockito.when(spy.getInstances("testService2")) .thenReturn(List.of(service2Instance2)); return spy; }}}Copy the code

Write test code to verify that the configuration is correct:

@ Test public void testConfigureCircuitBreaker () {/ / prevent the circuit breaker circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); / / call the two FeignClient ensure corresponding NamedContext initialized testService1Client. Anything (); testService2Client.anything(); / / test circuit breaker actual configuration, accord with our fill in the configuration of the List < CircuitBreaker > circuitBreakers = circuitBreakerRegistry. GetAllCircuitBreakers () asJava (); Set<String> collect = circuitBreakers.stream().map(CircuitBreaker::getName) .filter(name -> { try { return name.contains(TestService1Client.class.getMethod("anything").toGenericString()) || name.contains(TestService2Client.class.getMethod("anything").toGenericString()); } catch (NoSuchMethodException e) { return false; } }).collect(Collectors.toSet()); Assertions.assertEquals(collect.size(), 2); circuitBreakers.forEach(circuitBreaker -> { if (circuitBreaker.getName().contains(TestService1Client.class.getName())) {  Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) DEFAULT_FAILURE_RATE_THRESHOLD); Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), DEFAULT_MINIMUM_NUMBER_OF_CALLS); } else if (circuitBreaker.getName().contains(TestService2Client.class.getName())) { Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) TEST_SERVICE_2_FAILURE_RATE_THRESHOLD); Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), TEST_SERVICE_2_MINIMUM_NUMBER_OF_CALLS); }}); }Copy the code

Verify that the circuit breaker is open based on service and method.

Let’s add a method to TestService1Client:

@GetMapping("/status/500")
String testCircuitBreakerStatus500();
Copy the code

This method is bound to fail, causing the circuit breaker to open. After more than 2 failures (because the minimum number of requests that trigger the breaker open is configured to be 2), verify the status of the breaker:

@ Test public void testCircuitBreakerOpenBasedOnServiceAndMethod () {/ / prevent the circuit breaker circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); AtomicBoolean passed = new AtomicBoolean(false); for (int i = 0; i < 10; I++) {/ / multiple calls can lead to circuit breaker opens the try {System. Out. Println (testService1Client. TestCircuitBreakerStatus500 ()); } catch(Exception e) {} List<CircuitBreaker> circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava(); circuitBreakers.stream().filter(circuitBreaker -> { return circuitBreaker.getName().contains("testCircuitBreakerStatus500") && circuitBreaker.getName().contains("TestService1Client"); }).findfirst ().ifpresent (circuitBreaker -> {// verify that the circuitBreaker corresponding to the microservice and method is turned on if (circuitBreaker.getState().equals(CircuitBreaker.State.OPEN)) { passed.set(true); / / on the circuit breaker, call the other method, don't throw the breaker open abnormal testService1Client testAnything (); }}); } Assertions.assertTrue(passed.get()); }Copy the code

Thus, we have successfully verified that the circuit breaker is turned on based on the service and method.

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