Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 808
Oscar Gomez Avatar asked Apr 17 '17 14:04

Oscar Gomez


2 Answers

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.

like image 112
Ciaran George Avatar answered Oct 20 '22 15:10

Ciaran George


Hystrix is a tool that you accept is functional, much like Spring is a tool that you accept is functional. You do not need to unit test the ability of Hystrix to call your fallback method.

You should unit test the fallback method by calling it directly in a unit test.

That said, you are likely to want to test that Hystrix is actually calling the fallback method when you want Hystrix to call the fallback method; this will not be a unit test, it will be an integration test.

While it is possible to to write many integration tests using jUnit, it seems clear that Hystrix does not want to participate in jUnit tests.

I suggest that you should install your application in a development and/or qa test environment and test the Hystrix fallback functionality by forcing fallback on a running system.

like image 42
DwB Avatar answered Oct 20 '22 15:10

DwB