Back to Repositories

Testing Generic Fallback Method Compatibility in Netflix Hystrix

This test suite validates the generic fallback functionality in Netflix’s Hystrix library, focusing on proper type handling and fallback method definitions. It thoroughly tests both successful and failure scenarios for generic method and class implementations.

Test Coverage Overview

The test suite provides comprehensive coverage of generic fallback scenarios in Hystrix commands.

Key areas tested include:
  • Generic method definitions with various type constraints
  • Generic class definitions with type parameters
  • Successful fallback implementations
  • Invalid fallback method signatures
  • Complex generic type hierarchies

Implementation Analysis

The testing approach uses parameterized tests with JUnitParams to systematically verify generic fallback behavior. The suite implements separate test cases for method-level and class-level generic definitions, using Spring test framework integration for dependency management.

Key patterns include:
  • Abstract test base class for common functionality
  • Proxy-based command execution
  • Verification of Hystrix command execution events

Technical Details

Testing tools and configuration:
  • JUnit 4 with JUnitParams runner
  • Spring Test Context framework
  • Hystrix Command annotations
  • Custom proxy creation for command execution
  • HystrixInvokableInfo for command state verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices for complex generic type systems.

Notable practices include:
  • Systematic organization of test cases by category
  • Clear separation of success and failure scenarios
  • Comprehensive type hierarchy testing
  • Proper exception handling and verification
  • Reusable test infrastructure

netflix/hystrix

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

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

import com.netflix.hystrix.HystrixEventType;
import com.netflix.hystrix.HystrixInvokableInfo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException;
import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import rx.Completable;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

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

/**
 * Created by dmgcodevil.
 */
@RunWith(JUnitParamsRunner.class)
public abstract class BasicGenericFallbackTest extends BasicHystrixTest {

    @ClassRule
    public static final SpringClassRule SCR = new SpringClassRule();
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    protected abstract <T> T createProxy(Class<T> t);


    public Object[] methodGenericDefinitionSuccess() {
        return new Object[]{
                new Object[]{MethodGenericDefinitionSuccessCase0.class},
                new Object[]{MethodGenericDefinitionSuccessCase1.class},
                new Object[]{MethodGenericDefinitionSuccessCase2.class},
                new Object[]{MethodGenericDefinitionSuccessCase3.class},
                new Object[]{MethodGenericDefinitionSuccessCase4.class},
                new Object[]{MethodGenericDefinitionSuccessCase5.class},
                new Object[]{MethodGenericDefinitionSuccessCase6.class},
                new Object[]{MethodGenericDefinitionSuccessCase7.class},
        };
    }

    public Object[] methodGenericDefinitionFailure() {
        return new Object[]{
                new Object[]{MethodGenericDefinitionFailureCase0.class},
                new Object[]{MethodGenericDefinitionFailureCase1.class},
                new Object[]{MethodGenericDefinitionFailureCase2.class},
                new Object[]{MethodGenericDefinitionFailureCase3.class},
                new Object[]{MethodGenericDefinitionFailureCase4.class},
                new Object[]{MethodGenericDefinitionFailureCase5.class},
                new Object[]{MethodGenericDefinitionFailureCase6.class},
                new Object[]{MethodGenericDefinitionFailureCase7.class},
                new Object[]{MethodGenericDefinitionFailureCase8.class},
                new Object[]{MethodGenericDefinitionFailureCase9.class},
                new Object[]{MethodGenericDefinitionFailureCase10.class},
                new Object[]{MethodGenericDefinitionFailureCase11.class},

        };
    }

    public Object[] classGenericDefinitionSuccess() {
        return new Object[]{
                new Object[]{ClassGenericDefinitionSuccessCase0.class},
                new Object[]{ClassGenericDefinitionSuccessCase1.class},
        };
    }


    public Object[] classGenericDefinitionFailure() {
        return new Object[]{
                new Object[]{ClassGenericDefinitionFailureCase0.class},
                new Object[]{ClassGenericDefinitionFailureCase1.class},
        };
    }

    @Test
    @Parameters(method = "methodGenericDefinitionSuccess")
    public void testMethodGenericDefinitionSuccess(Class<?> type) {
        testXKindGenericDefinitionSuccess(type);
    }

    @Test(expected = FallbackDefinitionException.class)
    @Parameters(method = "methodGenericDefinitionFailure")
    public void testMethodGenericDefinitionFailure(Class<?> type) {
        Object p = createProxy(type);
        executeCommand(p);
    }

    @Test
    @Parameters(method = "classGenericDefinitionSuccess")
    public void testClassGenericDefinitionSuccess(Class<?> type) {
        testXKindGenericDefinitionSuccess(type);
    }

    @Test(expected = FallbackDefinitionException.class)
    @Parameters(method = "classGenericDefinitionFailure")
    public void testClassGenericDefinitionFailure(Class<?> type) {
        Object p = createProxy(type);
        executeCommand(p);
    }

    private void testXKindGenericDefinitionSuccess(Class<?> type) {
        Object p = createProxy(type);
        try {
            executeCommand(p);

            HystrixInvokableInfo<?> command = getHystrixCommandByKey("command");

            assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE));
            assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));

        } catch (FallbackDefinitionException e) {
            throw new AssertionError(MessageFormatter.format("Case class '{}' has failed. Reason:\n{}", type.getCanonicalName(), e).getMessage());
        }
    }

    /* ====================================================================== */
    /* ===================== GENERIC METHOD DEFINITIONS ===================== */
    /* =========================== SUCCESS CASES ============================ */

    public static class MethodGenericDefinitionSuccessCase0 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> T command() { throw new IllegalStateException(); }
        private <T> T fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase1 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable> T command() { throw new IllegalStateException(); }
        private <T extends Serializable> T fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase2 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable, T1 extends T> T1 command() { throw new IllegalStateException(); }
        private <T extends Serializable, T1 extends T> T1 fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase3 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable, T1 extends T> GenericEntity<? extends T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable, T1 extends T> GenericEntity<? extends T1> fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase4 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable & Comparable, T1 extends T> GenericEntity<? extends T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable & Comparable, T1 extends T> GenericEntity<? extends T1> fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase5 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable & Comparable, T1 extends GenericEntity<T>> GenericEntity<T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable & Comparable, T1 extends GenericEntity<T>> GenericEntity<T1> fallback() { return null; }
    }

    public static class MethodGenericDefinitionSuccessCase6 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> GenericEntity<T> command() { throw new IllegalStateException(); }
        private <T> GenericEntity<T> fallback() { return new GenericEntity<T>(); }
    }

    public static class MethodGenericDefinitionSuccessCase7 {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<? super Serializable> command() { throw new IllegalStateException(); }
        private GenericEntity<? super Serializable> fallback() { return null; }
    }


    /* ====================================================================== */
    /* ===================== GENERIC METHOD DEFINITIONS ===================== */
    /* =========================== FAILURE CASES ============================ */

    public static class MethodGenericDefinitionFailureCase0 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> T command() { throw new IllegalStateException(); }
        private String fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase1 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable> T command() { throw new IllegalStateException(); }
        private <T> T fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase2 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable> T command() { throw new IllegalStateException(); }
        private <T extends Comparable> T fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase3 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable, T1 extends T> GenericEntity<? extends T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable, T1 extends T> GenericEntity<T1> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase4 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable & Comparable, T1 extends T> GenericEntity<? extends T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable, T1 extends T> GenericEntity<? extends T1> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase5 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T extends Serializable & Comparable, T1 extends GenericEntity<T>> GenericEntity<T1> command() { throw new IllegalStateException(); }
        private <T extends Serializable & Comparable, T1 extends GenericEntity<String>> GenericEntity<T1> fallback() { return null;}
    }

    public static class MethodGenericDefinitionFailureCase6 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> GenericEntity<T> command() { throw new IllegalStateException(); }
        private GenericEntity<String> fallback() { return new GenericEntity<String>(); }
    }

    public static class MethodGenericDefinitionFailureCase7 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> Set<T> command() { throw new IllegalStateException(); }
        private <T> List<T> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase8 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> GenericEntity<Set<T>> command() { throw new IllegalStateException(); }
        private <T> GenericEntity<List<T>> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase9 {
        @HystrixCommand(fallbackMethod = "fallback")
        public <T> GenericEntity<List<Set<T>>> command() { throw new IllegalStateException(); }
        private <T> GenericEntity<List<List<T>>> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase10 {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<? super Serializable> command() { throw new IllegalStateException(); }
        private GenericEntity<? super Comparable> fallback() { return null; }
    }

    public static class MethodGenericDefinitionFailureCase11 {
        @HystrixCommand(fallbackMethod = "fallback")
        public Completable command() { throw new IllegalStateException(); }
        private void fallback() { return; }
    }

    /* ====================================================================== */
    /* ===================== GENERIC CLASS DEFINITIONS =====+================ */
    /* =========================== SUCCESS CASES ============================ */

    public static class ClassGenericDefinitionSuccessCase0<T> {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<T> command() { throw new IllegalStateException(); }
        private GenericEntity<T> fallback() { return new GenericEntity<T>(); }
    }

    public static class ClassGenericDefinitionSuccessCase1<T extends Serializable> {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<T> command() { throw new IllegalStateException(); }
        private GenericEntity<T> fallback() { return new GenericEntity<T>(); }
    }

    /* ====================================================================== */
    /* ===================== GENERIC CLASS DEFINITIONS =====+================ */
    /* =========================== FAILURE CASES ============================ */

    public static class ClassGenericDefinitionFailureCase0<T> {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<T> command() { throw new IllegalStateException(); }
        private <T> GenericEntity<T> fallback() { return new GenericEntity<T>(); }
    }

    public static class ClassGenericDefinitionFailureCase1<T extends Serializable> {
        @HystrixCommand(fallbackMethod = "fallback")
        public GenericEntity<T> command() { throw new IllegalStateException(); }
        private <T extends Serializable> GenericEntity<T> fallback() { return new GenericEntity<T>(); }
    }

    public static class GenericEntity<T> {
    }

    private static Object executeCommand(Object proxy) {
        try {
            Method method = proxy.getClass().getDeclaredMethod("command");
            return method.invoke(proxy);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof FallbackDefinitionException) {
                throw (FallbackDefinitionException) t;
            } else throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}