View Javadoc
1   package net.secodo.jcircuitbreaker.breaker.impl;
2   
3   import static org.hamcrest.core.IsEqual.equalTo;
4   import static org.hamcrest.core.IsInstanceOf.instanceOf;
5   import static org.hamcrest.core.IsNull.notNullValue;
6   
7   import static org.junit.Assert.assertThat;
8   import static org.junit.Assert.fail;
9   
10  import static org.mockito.Matchers.any;
11  import static org.mockito.Matchers.anyInt;
12  import static org.mockito.Matchers.anyString;
13  
14  import static org.mockito.Mockito.mock;
15  import static org.mockito.Mockito.never;
16  import static org.mockito.Mockito.times;
17  import static org.mockito.Mockito.verify;
18  import static org.mockito.Mockito.when;
19  
20  import java.io.IOException;
21  
22  import java.lang.reflect.Field;
23  
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.concurrent.Callable;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  import java.util.concurrent.atomic.AtomicInteger;
30  
31  import net.secodo.jcircuitbreaker.breaker.CircuitBreaker;
32  import net.secodo.jcircuitbreaker.breaker.ContextAwareCircuitBreaker;
33  import net.secodo.jcircuitbreaker.breaker.execution.ExecutedTask;
34  import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
35  import net.secodo.jcircuitbreaker.breakhandler.BreakHandler;
36  import net.secodo.jcircuitbreaker.breakhandler.exception.BreakHandlerException;
37  import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
38  import net.secodo.jcircuitbreaker.exception.CircuitBreakerException;
39  import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
40  
41  import net.secodo.jcircuitbreaker.task.Task;
42  import org.junit.Test;
43  
44  
45  /**
46   * NOTE: this really tests the AbstractCircuitBreaker -therefore maybe test should be renamed to
47   * AbstractCircuitBreakerTest
48   */
49  @SuppressWarnings("unchecked")
50  public class AbstractCircuitBreakerTest {
51    @Test
52    public void shouldStopMethodExecutionIfBreakerDecidesToBreak() throws TaskExecutionException {
53      // given
54      final long returnValue = 99999L;
55      final long fallbackReturnValue = 33333L;
56      final String paramValue1 = "VALUE IS HERE";
57      final Integer paramValue2 = 10;
58  
59      final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
60      final List<?> userData = mock(List.class);
61      final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
62      final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
63  
64      Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
65  
66      when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true); // break will happen
67      when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
68      when(
69        breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
70          any(Task.class),
71          any(BreakStrategy.class),
72          any(ExecutionContext.class))).thenReturn(
73        fallbackReturnValue);
74  
75      // when
76      final Long methodReturnValue =
77        new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
78          breakStrategy,
79          breakHandler,
80          userData);
81  
82      final Long methodReturnValueFromStatic =
83        new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
84          breakStrategy,
85          breakHandler,
86          userData); // also test static call of JCircuitBreaker
87  
88  
89      // then
90      verify(someTestObject, never()).someMethod(anyString(), anyInt());
91      verify(breakHandler, times(2)).onBreak(any(ContextAwareCircuitBreaker.class),
92        any(Task.class),
93        any(BreakStrategy.class),
94        any(ExecutionContext.class)); // +1 for normal call, + 1 for static call
95  
96      assertThat(methodReturnValue, equalTo(fallbackReturnValue));
97      assertThat(methodReturnValueFromStatic, equalTo(methodReturnValue)); // static call verification
98  
99    }
100 
101   @Test
102   public void shouldContinueAndExecuteMethodIfBreakerDecidesNotToBreak() throws TaskExecutionException {
103     // given
104     final Long returnValue = 9999L;
105     final long fallbackReturnValue = 3333L;
106     final String paramValue1 = "VALUE IS HERE";
107     final Integer paramValue2 = 10;
108 
109     final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
110     final List<?> userData = mock(List.class);
111     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
112     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
113 
114     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
115 
116     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false); // no break
117     when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
118     when(
119       breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
120         any(Task.class),
121         any(BreakStrategy.class),
122         any(ExecutionContext.class))).thenReturn(
123       fallbackReturnValue);
124 
125     // when
126     final Long methodReturnValue =
127       new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
128         breakStrategy,
129         breakHandler,
130         userData);
131 
132     final Long methodReturnValueFromStatic =
133       new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
134         breakStrategy,
135         breakHandler,
136         userData); // also test static JCircuitBreaker
137 
138 
139     // then
140     verify(someTestObject, times(2)).someMethod(anyString(), anyInt()); // 1 for normal call + 1 for static call
141     verify(breakHandler, never()).onBreak(any(ContextAwareCircuitBreaker.class),
142       any(Task.class),
143       any(BreakStrategy.class),
144       any(ExecutionContext.class));
145 
146     assertThat(methodReturnValue, equalTo(returnValue));
147     assertThat(methodReturnValueFromStatic, equalTo(methodReturnValue)); // static call verification
148 
149   }
150 
151   @Test
152   public void shouldThrowTaskExecutionExceptionIfTaskThrowsException() {
153     final String paramValue1 = "VALUE IS HERE";
154     final Integer paramValue2 = 10;
155 
156     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
157 
158     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
159     when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class); // any exception
160 
161     final Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
162 
163     final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
164     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false); // no break
165 
166     final SomeAbstractCircuitBreakerForTest<Long> defaultCircuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
167 
168     // when
169 
170 
171     try {
172       defaultCircuitBreaker.execute(methodCall, breakStrategy, breakHandler);
173       fail("TaskExecution exception should have been thrown");
174     } catch (TaskExecutionException e) {
175       // then
176       assertThat(e.getCause(), notNullValue());
177       assertThat(IOException.class.isInstance(e.getCause()), equalTo(true));
178       assertThat(e.getTaskException(), notNullValue());
179       assertThat(IOException.class.isInstance(e.getTaskException()), equalTo(true));
180     }
181 
182   }
183 
184   @Test
185   public void shouldThrowCircuitBreakerExceptionWhenUnexpectedExceptionOccurredWhenProcessingTheTask()
186                                                                                               throws Exception {
187     final String paramValue1 = "VALUE IS HERE";
188     final Integer paramValue2 = 10;
189 
190     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
191 
192     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
193     when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class); // any exception
194 
195     final Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
196 
197     final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
198     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false); // no break
199 
200     final ConcurrentHashMap<String, ExecutedTask<Long>> hashMapMock = mock(ConcurrentHashMap.class);
201     final Field tasksInProgressField = AbstractCircuitBreaker.class.getDeclaredField("tasksInProgress");
202     tasksInProgressField.setAccessible(true);
203 
204     final SomeAbstractCircuitBreakerForTest<Long> circuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
205     tasksInProgressField.set(circuitBreaker, hashMapMock);
206 
207     when(hashMapMock.put(any(String.class), any(ExecutedTask.class))).thenThrow(Exception.class);
208 
209     // when
210     try {
211       circuitBreaker.execute(methodCall, breakStrategy, breakHandler);
212       fail(CircuitBreakerException.class.getSimpleName() + " should have been thrown");
213     } catch (CircuitBreakerException e) {
214       // ok
215 
216     }
217 
218     verify(hashMapMock, times(1)).put(any(String.class), any(ExecutedTask.class));
219 
220   }
221 
222   @Test
223   public void shouldThrowCircuitBreakerExceptionWhenTaskHashcodeResultsInException() throws Exception {
224     final String paramValue1 = "VALUE IS HERE";
225     final Integer paramValue2 = 10;
226 
227     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
228 
229     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
230     when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class); // any exception
231 
232     AtomicBoolean hashcodeCalled = new AtomicBoolean(false);
233 
234     final Task<Long> methodCall = new Task<Long>() {
235       @Override
236       public Long execute() throws Exception {
237         return null;
238       }
239 
240       @Override
241       public int hashCode() {
242         hashcodeCalled.set(true);
243         throw new UnsupportedOperationException("abc");
244       }
245     };
246 
247     final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
248     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false); // no break
249 
250 
251     final SomeAbstractCircuitBreakerForTest<Long> circuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
252 
253     // when
254     try {
255       circuitBreaker.execute(methodCall, breakStrategy, breakHandler);
256 
257       // then
258 
259       fail(CircuitBreakerException.class.getSimpleName() + " should have been thrown");
260     } catch (CircuitBreakerException e) {
261       // ok
262       assertThat(e.getCause(), notNullValue());
263       assertThat(e.getCause(), instanceOf(UnsupportedOperationException.class));
264     }
265 
266     assertThat(hashcodeCalled.get(), equalTo(true));
267 
268   }
269 
270   @Test
271   public void shouldReuseExecutionContextWhenBreakHandlerCallsExecuteMethodOfCircuitBreaker() throws Exception {
272     // given
273     HashMap<Integer, Integer> executionContextCounter = new HashMap<>();
274 
275     BreakHandler<AtomicInteger> breakHandler = new BreakHandler<AtomicInteger>() {
276       @Override
277       public AtomicInteger onBreak(ContextAwareCircuitBreaker<AtomicInteger> circuitBreaker,
278                                    Task<AtomicInteger> task, BreakStrategy<AtomicInteger> breakStrategy,
279                                    ExecutionContext<AtomicInteger> executionContext) throws TaskExecutionException,
280                                                                                             CircuitBreakerException,
281                                                                                             BreakHandlerException {
282         final int hashCode = executionContext.hashCode();
283 
284         if (!executionContextCounter.containsKey(hashCode)) {
285           executionContextCounter.put(hashCode, 0);
286         }
287 
288         executionContextCounter.put(hashCode, executionContextCounter.get(hashCode) + 1);
289         return circuitBreaker.executeInContext(task, breakStrategy, this, executionContext);
290       }
291     };
292 
293 
294     BreakStrategy<AtomicInteger> breakStrategy = mock(BreakStrategy.class);
295     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true)
296     .thenReturn(true)
297     .thenReturn(true)
298     .thenReturn(false);
299 
300     final Task<AtomicInteger> task = mock(Task.class);
301     when(task.execute()).thenReturn(new AtomicInteger(999));
302 
303     final CircuitBreaker<AtomicInteger> breaker = new SomeAbstractCircuitBreakerForTest<>();
304 
305     // when
306     final AtomicInteger result = breaker.execute(task, breakStrategy, breakHandler);
307 
308     // then
309     verify(breakStrategy, times(4)).shouldBreak(any(Task.class), any(ExecutionContext.class));
310     assertThat(executionContextCounter.size(), equalTo(1));
311     assertThat(executionContextCounter.entrySet().iterator().next().getValue(), equalTo(3));
312 
313     assertThat(result.get(), equalTo(999));
314 
315 
316   }
317 
318 
319   class SomeTestClassWithSomeMethod {
320     SomeTestClassWithSomeMethod() {
321     }
322 
323     public Long someMethod(String paramValue1, Integer paramValue2) {
324       return (long) 1;
325     }
326 
327 
328   }
329 
330   class SomeAbstractCircuitBreakerForTest<R> extends AbstractCircuitBreaker<R> {
331   }
332 
333 
334 }