Spring's @Retryable not working when running JUnit Test

Programmermatt picture Programmermatt · Sep 13, 2016 · Viewed 17.3k times · Source

I have this test:

@RunWith(MockitoJUnitRunner.class)
public class myServiceTest {

@InjectMocks
myService subject;

private myService spy;

@Before
public void before() {
    spy = spy(subject);
}

@Test
public void testing() {

    when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
    spy.print1();
    verify(spy, times(3)).print2();
}

and then I have:

@Service("myService")
public class myService extends myAbstractServiceClass {


public String print1() {
    String temp = "";
    temp = print2();
    return temp;
}

 @Retryable
 public String print2() {
     return "completed";
 }
}

then I have this interface(which my abstractService implements):

public interface myServiceInterface {

    @Retryable(maxAttempts = 3)
    String print1() throws RuntimeException;

    @Retryable(maxAttempts = 3)
    String print2() throws RuntimeException;

}

but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?

Answer

Johannes Leimer picture Johannes Leimer · Sep 14, 2016

This is because you are not using the SpringJUnitClassRunner.

Mockito and your own classes are not taking the @Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.

This is from the SpringJUnit4ClassRunner JavaDoc:

SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations. To use this class, simply annotate a JUnit 4 based test class with @RunWith(SpringJUnit4ClassRunner.class) or @RunWith(SpringRunner.class).

You should restructure your test class at least to something like:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
    @Configuration
    @EnableRetry
    @Import(myService.class)
    public static class MyConfig {}
...

What am I doing there?

  1. activate the Spring JUnit hook
  2. specify the Spring context configuration class
  3. define the spring configuration and import your service as a bean
  4. enable the retryable annotation

Are there some other pitfalls?

  • Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
  • But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
  • You should reference the interface type of your service in your test: MyServiceInterface
  • And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized

Hope that helps.