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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With