Search code examples
javaunit-testingannotationshystrixmockrestserviceserver

How can I unit-test javanica @HystrixCommand annotated methods?


I am using javanica and annotating my hystrix command methods like this:

@HystrixCommand(groupKey="MY_GROUP", commandKey="MY_COMMAND" fallbackMethod="fallbackMethod")
public Object getSomething(Object request) {
....

And I am trying to unit tests my fallback methods, without having to call them directly, i.e. I would like to call the @HystrixCommand annotated method and let it flow naturally into the fallback after throwing a 500 error. This all works outside of unit tests.

In my unit tests I am using springs MockRestServiceServer to return 500 errors, this part is working, but Hystrix is not being initialized correctly on my unit tests. At the beginning of my test method I have:

HystrixRequestContext context = HystrixRequestContext.initializeContext();
myService.myHystrixCommandAnnotatedMethod();

After this I am trying to get any hystrix command by key and checking if there are any executed commands but the list is always empty, I am using this method:

public static HystrixInvokableInfo<?> getHystrixCommandByKey(String key) {
    HystrixInvokableInfo<?> hystrixCommand = null;
    System.out.println("Current request is " + HystrixRequestLog.getCurrentRequest());
    Collection<HystrixInvokableInfo<?>> executedCommands = HystrixRequestLog.getCurrentRequest()
            .getAllExecutedCommands();
    for (HystrixInvokableInfo<?> command : executedCommands) {
        System.out.println("executed command is " + command.getCommandGroup().name());
        if (command.getCommandKey().name().equals(key)) {
            hystrixCommand = command;
            break;
        }
    }
    return hystrixCommand;
}

I realize I am missing something in my unit tests initialization, can anyone point me in the right direction on how I can properly unit-test this?


Solution

  • Although you shouldn't necessarily UNIT test hystrix command. It's still useful to have a sort of spring hybrid test, I think point blank accepting the functionality when adding the annotation isn't correct. The test I created ensures that the circuit breaker opens on an exception.

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class HystrixProxyServiceTests {
    
        @MockBean
        private MyRepo myRepo;
    
        @Autowired
        private MyService myService;
    
        private static final String ID = “1”;
    
        @Before
        public void setup() {
            resetHystrix();
            openCircuitBreakerAfterOneFailingRequest();
        }
    
        @Test
        public void circuitBreakerClosedOnSuccess() throws IOException, InterruptedException {
    
            when(myRepo.findOneById(USER_ID1))
            .thenReturn(Optional.of(Document.builder().build()));
    
            myService.findOneById(USER_ID1);
            HystrixCircuitBreaker circuitBreaker = getCircuitBreaker();
            Assert.assertTrue(circuitBreaker.allowRequest());
    
            verify(myRepo, times(1)).findOneById(
                any(String.class));
        }
    
        @Test
        public void circuitBreakerOpenOnException() throws IOException, InterruptedException {
    
            when(myRepo.findOneById(ID))
                .thenThrow(new RuntimeException());
    
            try {
                myService.findOneById(ID);
            } catch (RuntimeException exception) {
                waitUntilCircuitBreakerOpens();
                HystrixCircuitBreaker circuitBreaker = getCircuitBreaker();
                Assert.assertFalse(circuitBreaker.allowRequest());
            }
    
            verify(myRepo, times(1)).findOneById(
                any(String.class));
        }
    
        private void waitUntilCircuitBreakerOpens() throws InterruptedException {
            Thread.sleep(1000);
        }
    
        private void resetHystrix() {
            Hystrix.reset();
        }
    
        private void warmUpCircuitBreaker() {
            myService.findOneById(USER_ID1);
        }
    
        public static HystrixCircuitBreaker getCircuitBreaker() {
            return HystrixCircuitBreaker.Factory.getInstance(getCommandKey());
        }
    
        private static HystrixCommandKey getCommandKey() {
            return HystrixCommandKey.Factory.asKey("findOneById");
        }
    
        private void openCircuitBreakerAfterOneFailingRequest() {
    
            ConfigurationManager.getConfigInstance().
                setProperty("hystrix.command.findOneById.circuitBreaker.requestVolumeThreshold", 1);
        }
    
    }
    

    Another little thing that tripped me up for a while was that I had entered the default annotations without a specific command key, however when the command keys are created they are created against the method name which is what I've specified above. For a complete example I've also added the annotation to show I didn't specify a commandKey.

    @HystrixCommand
    public Optional<Document> findOneById(final String id) {
        return this.myRepo.findOneById(id);
    }
    

    Hope this helps someone.