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.