Back to Repositories

Testing Default Fallback Mechanisms in Netflix Hystrix

This test suite validates the default fallback functionality in Netflix’s Hystrix library, focusing on both class-level and command-level fallback mechanisms. It verifies different fallback scenarios and their proper execution within the circuit breaker pattern.

Test Coverage Overview

The test suite provides comprehensive coverage of Hystrix’s fallback mechanisms, including:
  • Class-scope default fallbacks
  • Command-specific fallback overrides
  • Command-scope default fallbacks
  • Fallback execution verification
  • Multiple command execution scenarios
Tests verify both successful fallback execution and proper command failure handling.

Implementation Analysis

The testing approach utilizes JUnit’s framework features with Hystrix’s testing utilities.
  • Abstract test class pattern for flexible implementation
  • HystrixRequestLog validation for command execution
  • Event-based assertion patterns
  • Command key verification
  • Execution event chain validation

Technical Details

Testing infrastructure includes:
  • JUnit testing framework
  • Hystrix annotations (@DefaultProperties, @HystrixCommand)
  • HystrixRequestLog for command tracking
  • HystrixEventType for execution flow verification
  • CommonUtils helper methods

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Clear test method naming conventions
  • Proper test isolation using @Before setup
  • Comprehensive assertion chains
  • Multiple verification points per test
  • Abstract base class for reusable test logic

netflix/hystrix

hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicDefaultFallbackTest.java

            
package com.netflix.hystrix.contrib.javanica.test.common.fallback;

import com.netflix.hystrix.HystrixEventType;
import com.netflix.hystrix.HystrixInvokableInfo;
import com.netflix.hystrix.HystrixRequestLog;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest;
import org.junit.Before;
import org.junit.Test;


import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Created by dmgcodevil.
 */
public abstract class BasicDefaultFallbackTest extends BasicHystrixTest {

    private ServiceWithDefaultFallback serviceWithDefaultFallback;
    private ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback;

    protected abstract ServiceWithDefaultFallback createServiceWithDefaultFallback();

    protected abstract ServiceWithDefaultCommandFallback serviceWithDefaultCommandFallback();

    @Before
    public void setUp() throws Exception {
        serviceWithDefaultFallback = createServiceWithDefaultFallback();
        serviceWithDefaultCommandFallback = serviceWithDefaultCommandFallback();
    }

    @Test
    public void testClassScopeDefaultFallback() {
        String res = serviceWithDefaultFallback.requestString("");
        assertEquals(ServiceWithDefaultFallback.DEFAULT_RESPONSE, res);
        assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest()
                .getAllExecutedCommands().iterator().next();
        assertEquals("requestString", command.getCommandKey().name());

        // confirm that 'requestString' command has failed
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE));
        // and that default fallback waw successful
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
    }

    @Test
    public void testSpecificCommandFallbackOverridesDefault() {
        Integer res = serviceWithDefaultFallback.commandWithSpecificFallback("");
        assertEquals(Integer.valueOf(res), res);
        assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest()
                .getAllExecutedCommands().iterator().next();
        assertEquals("commandWithSpecificFallback", command.getCommandKey().name());

        // confirm that 'commandWithSpecificFallback' command has failed
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE));
        // and that default fallback waw successful
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
    }


    @Test
    public void testCommandScopeDefaultFallback() {
        Long res = serviceWithDefaultFallback.commandWithDefaultFallback(1L);
        assertEquals(Long.valueOf(0L), res);
        assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest()
                .getAllExecutedCommands().iterator().next();
        assertEquals("commandWithDefaultFallback", command.getCommandKey().name());

        // confirm that 'requestString' command has failed
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE));
        // and that default fallback waw successful
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
    }

    @Test
    public void testClassScopeCommandDefaultFallback() {
        String res = serviceWithDefaultCommandFallback.requestString("");
        assertEquals(ServiceWithDefaultFallback.DEFAULT_RESPONSE, res);


        assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixInvokableInfo<?> requestStringCommand = getHystrixCommandByKey("requestString");
        com.netflix.hystrix.HystrixInvokableInfo fallback = getHystrixCommandByKey("classDefaultFallback");

        assertEquals("requestString", requestStringCommand.getCommandKey().name());
        // confirm that command has failed
        assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
        assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
        // confirm that fallback was successful
        assertTrue(fallback.getExecutionEvents().contains(HystrixEventType.SUCCESS));
    }

    @Test
    public void testCommandScopeCommandDefaultFallback() {
        Long res = serviceWithDefaultCommandFallback.commandWithDefaultFallback(1L);
        assertEquals(Long.valueOf(0L), res);


        assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixInvokableInfo<?> requestStringCommand = getHystrixCommandByKey("commandWithDefaultFallback");
        com.netflix.hystrix.HystrixInvokableInfo fallback = getHystrixCommandByKey("defaultCommandFallback");

        assertEquals("commandWithDefaultFallback", requestStringCommand.getCommandKey().name());
        // confirm that command has failed
        assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
        assertTrue(requestStringCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
        // confirm that fallback was successful
        assertTrue(fallback.getExecutionEvents().contains(HystrixEventType.SUCCESS));
    }

    // case when class level default fallback
    @DefaultProperties(defaultFallback = "classDefaultFallback")
    public static class ServiceWithDefaultFallback {
        static final String DEFAULT_RESPONSE = "class_def";

        @HystrixCommand
        public String requestString(String str) {
            throw new RuntimeException();
        }

        public String classDefaultFallback() {
            return DEFAULT_RESPONSE;
        }

        @HystrixCommand(defaultFallback = "defaultCommandFallback")
        Long commandWithDefaultFallback(long l){
            throw new RuntimeException();
        }

        Long defaultCommandFallback(){
            return 0L;
        }

        @HystrixCommand(fallbackMethod = "specificFallback")
        Integer commandWithSpecificFallback(String str) {
            throw new RuntimeException();
        }

        Integer specificFallback(String str) {
            return 0;
        }
    }

    // class level default fallback is annotated with @HystrixCommand and should be executed as Hystrix command
    @DefaultProperties(defaultFallback = "classDefaultFallback")
    public static class ServiceWithDefaultCommandFallback {
        static final String DEFAULT_RESPONSE = "class_def";

        @HystrixCommand
        public String requestString(String str) {
            throw new RuntimeException();
        }

        @HystrixCommand
        public String classDefaultFallback() {
            return DEFAULT_RESPONSE;
        }

        @HystrixCommand(defaultFallback = "defaultCommandFallback")
        Long commandWithDefaultFallback(long l){
            throw new RuntimeException();
        }

        @HystrixCommand(fallbackMethod = "defaultCommandFallback")
        Long defaultCommandFallback(){
            return 0L;
        }
    }

}