11

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?

Tim Büthe
  • 62,884
  • 17
  • 92
  • 129
Oscar Gomez
  • 18,436
  • 13
  • 85
  • 118
  • Hi @Oscar did you find a solution? – Stefano L May 24 '17 at 14:01
  • 1
    @StefanoL nope, could not find any solution to this. I am still hoping someone has a way to do it right. – Oscar Gomez May 27 '17 at 23:33
  • 1
    I thought about writing separate test classes called XYZHystrixTest that actually boot the Spring Context partially with Hystrix in place. I do not see any other solution to solve this. – Stefano L May 31 '17 at 15:08
  • @StefanoL The Hystrix initialization in tests is what I am having trouble with, how are you solving that? – Oscar Gomez May 31 '17 at 17:45
  • not yet but basically it should come up when bootstrapping the (rudimentary) Spring context as well right? – Stefano L May 31 '17 at 18:01

4 Answers4

11

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.

Ciaran George
  • 571
  • 5
  • 19
7

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.

DwB
  • 37,124
  • 11
  • 56
  • 82
  • I agree with the part of writing integration test, but does it mean you suggest writing it using the @RunWith(SpringRunner.class), this would actually boot spring context and then we can test the implementation as shown in above answer. – Bilbo Baggins Jun 13 '19 at 06:18
  • I disagree, because there are many ways to misuse Hystrix, especially with annotations. For example, by catching exceptions in the hystrix-wrapped method. – Imaskar Aug 05 '20 at 11:56
0

Might be too Late.. But you can use the method to instantiate one before your code runs

I did it like this

public void warmUpCircuitBreaker() {
    HystrixCommandKey commandKey= HystrixCommandKey.Factory.asKey("test");
    HystrixCommandProperties.Setter setter = HystrixCommandProperties.defaultSetter();


    HystrixPropertiesCommandDefault hystrixPropertiesCommandDefault = new HystrixPropertiesCommandDefault(commandKey, setter);
    HystrixCommandGroupKey test = HystrixCommandGroupKey.Factory.asKey("Test");
    HystrixCircuitBreaker.Factory.getInstance(commandKey,
            test,
            hystrixPropertiesCommandDefault,
            HystrixCommandMetrics.getInstance(commandKey, test, hystrixPropertiesCommandDefault));

}

Call this method before calling SUT you can set all the required properties too

And in my tests I am setting the properties in order to test different scenarios

 ConfigurationManager.getConfigInstance()
            .setProperty("hystrix.command.test.circuitBreaker.forceClosed",
                    true);
Ramandeep S
  • 325
  • 1
  • 4
  • 15
-1

There's a problem I've run into a few times now: if the method signature of the wrapped method changes, there's no compile time or bootstrap checking that the fallback method is still callable. So if an int arg changes to a String, and I forget to change the fallback method's signature, I won't know until the application is running and the wrapped method is called.

bmadigan
  • 99
  • 1
  • 4