View Javadoc
1   package net.secodo.jcircuitbreaker.breakhandler.impl;
2   
3   import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
4   import net.secodo.jcircuitbreaker.task.Task;
5   import org.junit.Test;
6   import net.secodo.jcircuitbreaker.exception.CircuitBreakerException;
7   import net.secodo.jcircuitbreaker.breaker.CircuitBreaker;
8   import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
9   import net.secodo.jcircuitbreaker.breaker.impl.DefaultCircuitBreaker;
10  import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
11  import java.lang.reflect.Field;
12  import java.util.concurrent.Callable;
13  import static junit.framework.TestCase.fail;
14  import static org.hamcrest.CoreMatchers.equalTo;
15  import static org.junit.Assert.assertThat;
16  import static org.junit.Assert.assertTrue;
17  import static org.mockito.Matchers.any;
18  import static org.mockito.Mockito.mock;
19  import static org.mockito.Mockito.times;
20  import static org.mockito.Mockito.verify;
21  import static org.mockito.Mockito.when;
22  
23  
24  @SuppressWarnings("unchecked")
25  public class StatefulRetryHandlerTest {
26    @Test
27    public void shouldResultInRetryExceptionWhenBreakStrategyBlocksTaskExecutionAndNumberOfRetryAttemptsExceeds()
28      throws TaskExecutionException, NoSuchFieldException, IllegalAccessException {
29      // given
30      final int maxNumberOfRetries = 3;
31      final int wantedNumberOfTaskInvocations = maxNumberOfRetries + 1; //first one, and 3 more retries
32  
33  
34      final StatefulRetryHandler<Long> retryHandler = new StatefulRetryHandler<>(maxNumberOfRetries);
35      final CircuitBreaker<Long> circuitBreaker = new DefaultCircuitBreaker<>();
36  
37      final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
38      when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true); // strategy always does not allow execution
39  
40      final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
41  
42      Task<Long> methodCall = () -> someTestObject.someMethod("44444444", 222222);
43      // when
44  
45      RetryHandlerException resultedException = null;
46  
47      try {
48        circuitBreaker.execute(methodCall, breakStrategy, retryHandler, new Object());
49  
50        fail("Expected " + RetryHandlerException.class.getSimpleName() + " to occur, but no exception was thrown"); // because retry should fail
51      } catch (RetryHandlerException e) {
52        resultedException = e;
53      }
54  
55  
56      // then
57      assertTrue(RetryHandlerException.class.isInstance(resultedException));
58      verify(breakStrategy, times(wantedNumberOfTaskInvocations)).shouldBreak(any(Task.class), any(ExecutionContext.class));
59  
60      assertThat(getCurrentRetryAttempt(retryHandler), equalTo(maxNumberOfRetries));
61  
62    }
63  
64    @Test
65    public void shouldRetryOnceInCaseOfStrategyDoesNotAllowTaskExecutionAndOnlyOneExtraInvocatinIsAllowedByRetryHandler()
66      throws TaskExecutionException, NoSuchFieldException, IllegalAccessException {
67      // given
68      final int maxNumberOfRetries = 1;
69      final int wantedNumberOfTaskInvocations = 2; //first one, and 1 more retry
70  
71  
72      final StatefulRetryHandler<Long> retryHandler = new StatefulRetryHandler<>();
73      final CircuitBreaker circuitBreaker = new DefaultCircuitBreaker();
74      final BreakStrategy breakStrategy = mock(BreakStrategy.class);
75      final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
76  
77      when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true); // strategy always does not allow execution
78  
79      Task<Long> methodCall = () -> someTestObject.someMethod("44444444", 222222);
80  
81      // when
82      RetryHandlerException resultedException = null;
83  
84      try {
85        circuitBreaker.execute(methodCall, breakStrategy, retryHandler, new Object());
86  
87        fail("Expected " + CircuitBreakerException.class.getSimpleName() + " to occur, but no exception was thrown"); // because retry should fail
88      } catch (RetryHandlerException e) {
89        resultedException = e;
90      }
91  
92      // then
93      assertTrue(RetryHandlerException.class.isInstance(resultedException));
94      verify(breakStrategy, times(wantedNumberOfTaskInvocations)).shouldBreak(any(Task.class), any(ExecutionContext.class));
95  
96      assertThat(getCurrentRetryAttempt(retryHandler), equalTo(maxNumberOfRetries));
97  
98    }
99  
100   @Test
101   public void shouldRetryInCaseOfStrategyDoesNotAllowTaskExecutionThreeTimesButAllowFourthTime()
102                                                                                         throws TaskExecutionException,
103                                                                                                RetryHandlerException,
104                                                                                                NoSuchFieldException,
105                                                                                                IllegalAccessException {
106     // given
107     final int maxNumberOfRetries = 3;
108     final int wantedNumberOfInvocations = maxNumberOfRetries + 1;
109     final String paramValue1 = "44444444";
110     final int paramValue2 = 222222;
111     final Long returnValue = 88888L;
112 
113 
114     final StatefulRetryHandler<Long> retryHandler = new StatefulRetryHandler<>(maxNumberOfRetries);
115     final CircuitBreaker<Long> circuitBreaker = new DefaultCircuitBreaker<>();
116     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
117     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
118 
119     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true)
120     .thenReturn(true)
121     .thenReturn(true)
122     .thenReturn(false); // strategy always does not allow execution
123     when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
124 
125     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
126 
127     // when
128     // RetryHandlerException should not be thrown because the number of retry attempts does not excpeed allowed maximum
129     final Long resultingReturnValue = circuitBreaker.execute(methodCall, breakStrategy, retryHandler, new Object());
130 
131     // then
132     verify(breakStrategy, times(wantedNumberOfInvocations)).shouldBreak(any(Task.class), any(ExecutionContext.class));
133     assertThat(getCurrentRetryAttempt(retryHandler), equalTo(3)); // because BreakStrategy returns true three times, and false 4th time
134 
135     verify(someTestObject, times(1)).someMethod(paramValue1, paramValue2);
136     assertThat(resultingReturnValue, equalTo(returnValue));
137 
138   }
139 
140   @Test
141   public void shouldCallOnRetryCallbackForEachRetryIfCallbackWasDefined() throws TaskExecutionException {
142     // given
143     final int maxNumberOfRetries = 3;
144     final String paramValue1 = "44444444";
145     final int paramValue2 = 222222;
146 
147     final RetryHandlerOnRetryCallback callback = mock(RetryHandlerOnRetryCallback.class);
148     final StatefulRetryHandler<Long> retryHandler = new StatefulRetryHandler<>(maxNumberOfRetries, callback);
149 
150     final CircuitBreaker<Long> circuitBreaker = new DefaultCircuitBreaker();
151     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
152     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
153 
154     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true);
155 
156     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
157 
158     // when
159     final Long resultingReturnValue;
160     try {
161       resultingReturnValue = circuitBreaker.execute(methodCall, breakStrategy, retryHandler, new Object());
162       fail("RetryHandlerException should have been thrown"); // because breakStrategy always returns true, the retry handler should throw exception after it exceeded total number of retry attempts
163     } catch (RetryHandlerException e) {
164       // ok we should get here
165 
166     }
167 
168     // then
169     verify(callback, times(maxNumberOfRetries)).onRetry(any(Integer.class), any(ExecutionContext.class));
170     verify(breakStrategy, times(maxNumberOfRetries + 1)).shouldBreak(any(Task.class), any(ExecutionContext.class)); // +1 for first invocation (before retry)
171 
172   }
173 
174   class SomeTestClassWithSomeMethod {
175     SomeTestClassWithSomeMethod() {
176     }
177 
178     public Long someMethod(String paramValue1, Integer paramValue2) {
179       return (long) 1;
180     }
181 
182 
183   }
184 
185   private int getCurrentRetryAttempt(StatefulRetryHandler retryHandler) throws NoSuchFieldException, IllegalAccessException {
186     Field field = StatefulRetryHandler.class.getDeclaredField("currentRetryAttempt");
187     field.setAccessible(true);
188     return (int) field.get(retryHandler);
189 
190   }
191 }