View Javadoc
1   package net.secodo.jcircuitbreaker.breaker.impl;
2   
3   import static org.hamcrest.core.IsEqual.equalTo;
4   
5   import static org.junit.Assert.assertThat;
6   
7   import static org.mockito.Matchers.any;
8   import static org.mockito.Matchers.anyInt;
9   import static org.mockito.Matchers.anyString;
10  
11  import static org.mockito.Mockito.mock;
12  import static org.mockito.Mockito.never;
13  import static org.mockito.Mockito.times;
14  import static org.mockito.Mockito.verify;
15  import static org.mockito.Mockito.verifyZeroInteractions;
16  import static org.mockito.Mockito.when;
17  
18  import java.util.Iterator;
19  import java.util.LinkedHashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import net.secodo.jcircuitbreaker.breaker.ContextAwareCircuitBreaker;
24  import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
25  import net.secodo.jcircuitbreaker.breakhandler.BreakHandler;
26  import net.secodo.jcircuitbreaker.breakhandler.BreakHandlerFactory;
27  import net.secodo.jcircuitbreaker.breakhandler.OnePerExecutionHandlerFactory;
28  import net.secodo.jcircuitbreaker.breakhandler.impl.StatefulRetryHandler;
29  import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
30  import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
31  import net.secodo.jcircuitbreaker.task.Task;
32  
33  import org.junit.Test;
34  
35  
36  @SuppressWarnings({ "unchecked" })
37  public class ReusableCircuitBreakerTest {
38    @Test
39    public void shouldConstructWorkingCircuitBreakerUsingBuilder() throws TaskExecutionException {
40      // given
41      final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
42      when(someTestObject.someMethod(anyString(), anyInt())).thenReturn(7777L);
43  
44      final Task<Long> methodCall = () -> someTestObject.someMethod("asdsas", 13213);
45  
46      abstract class Cpu {
47        abstract int getCpuLoad();
48      }
49  
50      final Cpu cpu = mock(Cpu.class);
51  
52      ReusableCircuitBreaker<Long> breaker = ReusableCircuitBreaker.builder()
53        .withBreakStrategy((task, executionContext) -> cpu.getCpuLoad() > 80)
54        .withBreakHandler((circuitBreaker, task, breakStrategy, executionContext) -> -1L).build();
55  
56      when(cpu.getCpuLoad()).thenReturn(60).thenReturn(81);
57  
58      // when
59      Long returnValue = breaker.execute(methodCall);
60  
61      // then
62      assertThat(returnValue, equalTo(7777L)); // first time CPU was under 80% so the method was executed
63  
64      // when
65      returnValue = breaker.execute(methodCall);
66  
67      // then
68      assertThat(returnValue, equalTo(-1L)); // second time CPU was over 80% so the method was no executed and value returned by break handler was used
69  
70  
71    }
72  
73    @Test
74    public void shouldConstructWorkingCircuitBreakerUsingCheckedBuilder() throws TaskExecutionException {
75      // given
76      final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
77      when(someTestObject.someMethod(anyString(), anyInt())).thenReturn(7777L);
78  
79      final Task<Long> methodCall = () -> someTestObject.someMethod("asdsas", 13213);
80  
81      abstract class Cpu {
82        abstract int getCpuLoad();
83      }
84  
85      final Cpu cpu = mock(Cpu.class);
86  
87      ReusableCircuitBreaker<Long> breaker = ReusableCircuitBreaker.<Long>builderP()
88        .withBreakStrategy((task,executionContext) -> cpu.getCpuLoad() > 80)
89        .withBreakHandler((circuitBreaker, task, breakStrategy, executionContext) -> -1L)
90        .build();
91  
92      when(cpu.getCpuLoad()).thenReturn(60).thenReturn(81);
93  
94      // when
95      Long returnValue = breaker.execute(methodCall);
96  
97      // then
98      assertThat(returnValue, equalTo(7777L)); // first time CPU was under 80% so the method was executed
99  
100     // when
101     returnValue = breaker.execute(methodCall);
102 
103     // then
104     assertThat(returnValue, equalTo(-1L)); // second time CPU was over 80% so the method was no executed and value returned by break handler was used
105 
106 
107   }
108 
109   @Test
110   public void shouldStopMethodExecutionIfBreakerDecidesToBreak() throws TaskExecutionException {
111     // given
112     final long returnValue = 99999L;
113     final long fallbackReturnValue = 33333L;
114     final String paramValue1 = "VALUE IS HERE";
115     final Integer paramValue2 = 10;
116 
117     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
118     final List<?> userData = mock(List.class);
119     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
120     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
121 
122     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
123 
124     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true); // break will happen
125     when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
126     when(
127       breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
128         any(Task.class),
129         any(BreakStrategy.class),
130         any(ExecutionContext.class))).thenReturn(
131       fallbackReturnValue);
132 
133     // when
134     final ReusableCircuitBreaker<Long> genericCBTest = new ReusableCircuitBreaker<>(breakStrategy,
135       breakHandler);
136     final Long methodReturnValue = genericCBTest.execute(methodCall, userData);
137 
138 
139     // then
140     verify(someTestObject, never()).someMethod(anyString(), anyInt());
141     verify(breakHandler, times(1)).onBreak(any(ContextAwareCircuitBreaker.class),
142       any(Task.class),
143       any(BreakStrategy.class),
144       any(ExecutionContext.class));
145 
146     assertThat(methodReturnValue, equalTo(fallbackReturnValue));
147 
148   }
149 
150   @Test
151   public void shouldContinueAndExecuteMethodIfBreakerDecidesNotToBreak() throws TaskExecutionException {
152     // given
153     final Long returnValue = 9999L;
154     final long fallbackReturnValue = 3333L;
155     final String paramValue1 = "VALUE IS HERE";
156     final Integer paramValue2 = 10;
157 
158     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
159     final List<?> userData = mock(List.class);
160     final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
161     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
162 
163     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
164 
165     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false); // no break
166     when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
167     when(
168       breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
169         any(Task.class),
170         any(BreakStrategy.class),
171         any(ExecutionContext.class))).thenReturn(
172       fallbackReturnValue);
173 
174     // when
175     final ReusableCircuitBreaker<Long> genericCB = new ReusableCircuitBreaker<>(breakStrategy,
176       breakHandler);
177     final Long methodReturnValue = genericCB.execute(methodCall, userData);
178 
179 
180     // then
181     verify(someTestObject, times(1)).someMethod(anyString(), anyInt());
182     verify(breakHandler, never()).onBreak(any(ContextAwareCircuitBreaker.class),
183       any(Task.class),
184       any(BreakStrategy.class),
185       any(ExecutionContext.class));
186 
187     assertThat(methodReturnValue, equalTo(returnValue));
188   }
189 
190   @Test
191   public void shouldNotShareStateBetweenExecutionsWhenOnePerRequestHandlerFactoryIsUsedWithNotReusableBreakHandler()
192     throws TaskExecutionException {
193     // given
194     final Long returnValue = 9999L;
195     final long fallbackReturnValue = 3333L;
196     final String paramValue1 = "VALUE IS HERE";
197     final Integer paramValue2 = 10;
198 
199     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
200 
201     // prepare break handler which is not reusable
202     LinkedHashMap<Integer, Integer> invocationsCounted = new LinkedHashMap<>();
203 
204 
205     class NotReusableBreakHandler implements BreakHandler<Long> {
206       @Override
207       public Long onBreak(ContextAwareCircuitBreaker<Long> circuitBreaker, Task<Long> task,
208                           BreakStrategy<Long> breakStrategy, ExecutionContext<Long> executionContext)
209                    throws TaskExecutionException {
210         int thisObject = this.hashCode();
211 
212         if (!invocationsCounted.containsKey(thisObject)) {
213           invocationsCounted.put(thisObject, 1);
214         } else {
215           int count = invocationsCounted.get(thisObject);
216           invocationsCounted.put(thisObject, count + 1);
217         }
218 
219 
220         return fallbackReturnValue;
221       }
222     }
223 
224 
225     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
226     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
227 
228     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true).thenReturn(true).thenReturn(true);
229 
230     // factory below (each invocation should create new handler) instead of using the same instance of break handler
231     final ReusableCircuitBreaker<Long> breaker = new ReusableCircuitBreaker<>(breakStrategy,
232       (OnePerExecutionHandlerFactory<Long>) (task, executionContext) -> new NotReusableBreakHandler());
233 
234     // when
235     Long methodReturnValue = breaker.execute(methodCall);
236 
237 
238     // then
239     assertThat(invocationsCounted.size(), equalTo(1)); // only one execution of CircuitBreaker took place
240     assertThat(invocationsCounted.entrySet().iterator().next().getValue(),
241       equalTo(1)); // break handler executed only once
242 
243     verifyZeroInteractions(someTestObject);
244 
245     assertThat(methodReturnValue, equalTo(fallbackReturnValue));
246 
247     // NOW what if another invocation happens
248 
249     // when
250     methodReturnValue = breaker.execute(methodCall);
251 
252     // then
253     assertThat(invocationsCounted.size(), equalTo(2)); // now two different executions took place
254 
255     final Iterator<Map.Entry<Integer, Integer>> invocationMapIt = invocationsCounted.entrySet().iterator();
256     assertThat(invocationMapIt.next().getValue(),
257       equalTo(1)); // first execution called 1 time to break handler (no change here)
258     assertThat(invocationMapIt.next().getValue(), equalTo(1)); // second execution called 1 times break handler
259 
260     verifyZeroInteractions(someTestObject);
261 
262     assertThat(methodReturnValue, equalTo(fallbackReturnValue));
263 
264   }
265 
266   @Test
267   public void shouldNotShareStateBetweenExecutionsWhenUsingOnePerExecutionHandlerFactoryWithStatefulRetryHandler()
268     throws TaskExecutionException {
269     // given
270     final Long returnValue = 9999L;
271     final String paramValue1 = "VALUE IS HERE";
272     final Integer paramValue2 = 10;
273 
274     // prepare counting retry handler which is not reusable
275     LinkedHashMap<Integer, Integer> invocationsCounted = new LinkedHashMap<>();
276 
277     final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
278     when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
279 
280     Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
281 
282     class MyStatefulRetryHandler extends StatefulRetryHandler<Long> {
283       public MyStatefulRetryHandler(int maxNumberOfRetries) {
284         super(maxNumberOfRetries);
285       }
286 
287       @Override
288       protected void onRetry(int currentRetryAttempt, Task<Long> task, ExecutionContext<Long> executionContext) {
289         int thisObject = this.hashCode();
290 
291         if (!invocationsCounted.containsKey(thisObject)) {
292           invocationsCounted.put(thisObject, 1);
293         } else {
294           int count = invocationsCounted.get(thisObject);
295           invocationsCounted.put(thisObject, count + 1);
296         }
297       }
298     }
299 
300     BreakHandlerFactory<Long> breakHandlerFactory = (OnePerExecutionHandlerFactory<Long>) (task, executionContext) ->
301       new MyStatefulRetryHandler(10);
302 
303     final BreakStrategy breakStrategy = mock(BreakStrategy.class);
304     when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true)
305     .thenReturn(true)
306     .thenReturn(true)
307     .thenReturn(false) // three invocations of breakHandler (3 x thenReturn=true) until the actual method gets executed (thenReturn=false)
308     .thenReturn(true)
309     .thenReturn(true)
310     .thenReturn(false); // for second execution of circuit breaker there are 2 invocations of breakHandler (2 x thenReturn=true) until the actual method gets executed
311 
312     final ReusableCircuitBreaker<Long> breaker = new ReusableCircuitBreaker<>(breakStrategy,
313       breakHandlerFactory); // factory method (each invocation should create new handler) used instead of using the same
314 
315     // when
316     Long methodReturnValue = breaker.execute(methodCall);
317 
318 
319     // then
320     assertThat(invocationsCounted.size(), equalTo(1)); // only one execution of CircuitBreaker took place so only one instance of MyStatefulRetryHandler should have been created
321     assertThat(invocationsCounted.entrySet().iterator().next().getValue(), equalTo(3)); // create instance should have been called 3 times...
322 
323     verify(someTestObject, times(1)).someMethod(any(String.class), any(Integer.class)); // ... until target-method was executed
324 
325     assertThat(methodReturnValue, equalTo(returnValue));
326 
327     // NOW what if another invocation happens
328 
329     // when
330     methodReturnValue = breaker.execute(methodCall);
331 
332     // then
333     assertThat(invocationsCounted.size(), equalTo(2)); // now two different executions took place
334 
335     final Iterator<Map.Entry<Integer, Integer>> invocationMapIt = invocationsCounted.entrySet().iterator();
336     assertThat(invocationMapIt.next().getValue(), equalTo(3)); // first execution called 3 times break handler (no change here)
337     assertThat(invocationMapIt.next().getValue(), equalTo(2)); // second execution called 2 times break handler
338 
339     verify(someTestObject, times(2)).someMethod(any(String.class), any(Integer.class)); // two executions were made
340 
341     assertThat(methodReturnValue, equalTo(returnValue));
342 
343   }
344 
345 
346   class SomeTestClassWithSomeMethod {
347     SomeTestClassWithSomeMethod() {
348     }
349 
350     public Long someMethod(String paramValue1, Integer paramValue2) {
351       return (long) 1;
352     }
353 
354 
355   }
356 
357 }