Search code examples
tddspring-testhystrixspring-cloud-netflixcircuit-breaker

Test drive Hystrix Circuit Breaker configuration


Our application is written in anit-fragile manner by implementing circuit breaker pattern using Hystrix.

The whole of the application is created using test driven practice but is stuck at the point where we need to implement the circuit breaker strategy by configuring the same on the methods.

Below is the sample configuration used by us -

@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "8"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "25"),
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")},
        fallbackMethod = "retrieveMapFallback")

Can anyone comment, if there is an available feature or opportunity to test drive it in my Integration Test (which loads the whole of WebApplicationContext, and hence is aware of all configurations available with the application)?

Or if this is not at all possible to be verified in my app context?

Any inputs will be of value.


Solution

  • You can test your Hystrix circuit breaker configuration.

    For instance, take a look at this example application with Spring Boot 1.4:

        import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
        import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
        import org.springframework.stereotype.Component;
    
        @EnableCircuitBreaker
        @SpringBootApplication
        public class HystrixDemo {
    
            public static void main(String[] args) {
                SpringApplication.run(HystrixDemo.class, args);
            }
    
            @Component
            static class MyService {
    
                static final String COMMAND_KEY = "MyCommandKey";
    
                private final Outbound outbound;
    
                MyService(Outbound outbound) {
                    this.outbound = outbound;
                }
    
                @HystrixCommand(
                        commandKey = COMMAND_KEY,
                        commandProperties = {
                            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2016")
                        })
                void process() {
                    outbound.call();
                }
            }
    
            interface Outbound {
                void call();
            }
        }
    

    Your configuration tests may look like this one:

        import com.netflix.hystrix.HystrixCommandKey;
        import com.netflix.hystrix.HystrixCommandMetrics;
        import com.netflix.hystrix.HystrixCommandProperties;
        import org.junit.Before;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.boot.test.mock.mockito.MockBean;
        import org.springframework.test.context.junit4.SpringRunner;
    
        import static org.junit.Assert.assertTrue;
    
        @RunWith(SpringRunner.class)
        @SpringBootTest
        public class MyServiceCircuitBreakerConfigurationTests {
    
            @Autowired
            private HystrixDemo.MyService myService;
    
            @MockBean
            private HystrixDemo.Outbound outbound;
    
            @Before
            public void setup() {
                warmUpCircuitBreaker();
            }
    
            @Test
            public void shouldHaveCustomTimeout() {
                assertTrue(getCircuitBreakerCommandProperties().executionTimeoutInMilliseconds().get() == 2016);
            }
    
            private void warmUpCircuitBreaker() {
                myService.process();
            }
    
            public static HystrixCommandProperties getCircuitBreakerCommandProperties() {
                return HystrixCommandMetrics.getInstance(getCommandKey()).getProperties();
            }
    
            private static HystrixCommandKey getCommandKey() {
                return HystrixCommandKey.Factory.asKey(HystrixDemo.MyService.COMMAND_KEY);
            }
        }
        
    

    In addition, if you want to test circuit breaker you can take a look at this test:

        import com.netflix.config.ConfigurationManager;
        import com.netflix.hystrix.Hystrix;
        import com.netflix.hystrix.HystrixCircuitBreaker;
        import com.netflix.hystrix.HystrixCommandKey;
        import org.junit.Before;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.boot.test.mock.mockito.MockBean;
        import org.springframework.test.context.junit4.SpringRunner;
    
        import static org.junit.Assert.assertFalse;
        import static org.junit.Assert.assertTrue;
        import static org.junit.Assert.fail;
        import static org.mockito.BDDMockito.willThrow;
    
        @RunWith(SpringRunner.class)
        @SpringBootTest
        public class MyServiceCircuitBreakerTests {
    
            @Autowired
            private HystrixDemo.MyService myService;
    
            @MockBean
            private HystrixDemo.Outbound outbound;
    
            @Before
            public void setup() {
                resetHystrix();
                warmUpCircuitBreaker();
                openCircuitBreakerAfterOneFailingRequest();
            }
    
            @Test
            public void shouldTripCircuit() throws InterruptedException {
                willThrow(new RuntimeException()).given(outbound).call();
    
                HystrixCircuitBreaker circuitBreaker = getCircuitBreaker();
    
                // demonstrates circuit is actually closed
                assertFalse(circuitBreaker.isOpen());
                assertTrue(circuitBreaker.allowRequest());
    
                try {
                    myService.process();
                    fail("unexpected");
                } catch (RuntimeException exception) {
                    waitUntilCircuitBreakerOpens();
                    assertTrue(circuitBreaker.isOpen());
                    assertFalse(circuitBreaker.allowRequest());
                }
            }
    
            private void waitUntilCircuitBreakerOpens() throws InterruptedException {
                /* one second is almost sufficient
                   borrowed from https://github.com/Netflix/Hystrix/blob/v1.5.5/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java#L140
                 */
                Thread.sleep(1000);
            }
    
            private void resetHystrix() {
                Hystrix.reset();
            }
    
            private void warmUpCircuitBreaker() {
                myService.process();
            }
    
            public static HystrixCircuitBreaker getCircuitBreaker() {
                return HystrixCircuitBreaker.Factory.getInstance(getCommandKey());
            }
    
            private static HystrixCommandKey getCommandKey() {
                return HystrixCommandKey.Factory.asKey(HystrixDemo.MyService.COMMAND_KEY);
            }
    
            private void openCircuitBreakerAfterOneFailingRequest() {
                ConfigurationManager.getConfigInstance().setProperty("hystrix.command." + HystrixDemo.MyService.COMMAND_KEY + ".circuitBreaker.requestVolumeThreshold", 1);
            }
        }