package com.kendelong.util.circuitbreaker; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import org.aspectj.lang.ProceedingJoinPoint; import org.junit.Before; import org.junit.Test; public class CircuitBreakerAspectTest { private CircuitBreakerAspect aspect; private ProceedingJoinPoint mockPjp; private Object returnValue; @Before public void setUp() { aspect = new CircuitBreakerAspect(); mockPjp = createMock(ProceedingJoinPoint.class); returnValue = new Object(); } @Test public void testNormalCallGoesThrough() throws Throwable { expect(mockPjp.proceed()).andReturn(returnValue); replay(mockPjp); Object result = aspect.applyCircuitBreaker(mockPjp); assertSame(returnValue, result); verify(mockPjp); } @Test public void testFailsFastAfterInvocationFailures() throws Throwable { int threshold = 3; aspect.setFailureThreshold(threshold); expect(mockPjp.proceed()).andThrow(new RuntimeException()).times(threshold); replay(mockPjp); for(int i = 0; i < threshold; i++) { try { aspect.applyCircuitBreaker(mockPjp); } catch(Throwable e) // NOPMD { // the first several calls should fail } } // Now, after "threshold" number of calls, the circuit breaker should open, and throw a CBException try { aspect.applyCircuitBreaker(mockPjp); } catch(CircuitBreakerException cbe) // NOPMD { // yahoo, this is what we want } catch(Throwable t) { fail("Caught the wrong exception on the last call; circuit breaker did not open properly"); } } @Test public void testResetsAfterTimeoutInterval() throws Throwable { // put it in open state aspect.tripBreaker(); int timeout = 100; aspect.setRecoveryTimeout(timeout); // Expect successful completion on second call expect(mockPjp.proceed()).andReturn(returnValue); replay(mockPjp); try { aspect.applyCircuitBreaker(mockPjp); } catch(CircuitBreakerException e) // NOPMD { // this is expected; circuit is still open } Thread.sleep((long) (timeout*1.2)); Object value = aspect.applyCircuitBreaker(mockPjp); verify(mockPjp); assertSame(value, returnValue); assertEquals("Wrong state", "ClosedState", aspect.getCurrentState()); } @Test public void testHalfOpenGoesClosedIfItSucceeds() throws Throwable { // put it in the HalfOpen state aspect.attemptReset(); // This is how many times to call after the reset int count = 3; // Expect successful completion on second call int totalCalls = count + 1; expect(mockPjp.proceed()).andReturn(returnValue).times(totalCalls); replay(mockPjp); aspect.applyCircuitBreaker(mockPjp); // this call succeeds and resets the breaker for(int i = 0; i < count; i++) aspect.applyCircuitBreaker(mockPjp); // Now we are in closed state so it should keep working verify(mockPjp); } @Test public void testHalfOpenGoesOpenIfItFails() throws Throwable { // put it in the HalfOpen state aspect.attemptReset(); expect(mockPjp.proceed()).andThrow(new RuntimeException()); replay(mockPjp); try { aspect.applyCircuitBreaker(mockPjp); // this call fails and should trip breaker fail("This is supposed to fail; the test is hosed"); } catch(RuntimeException e1) // NOPMD { // this is ok, just keep swimming... } try { aspect.applyCircuitBreaker(mockPjp); fail("Breaker didn't open again"); } catch(CircuitBreakerException e) // NOPMD { // should be here } catch(Throwable t) { fail("Caught wrong exception, should be CircuitBreakerException " + t.getClass().getName()); } verify(mockPjp); } @Test public void testResetGoesToClosedState() { aspect.tripBreaker(); assertEquals("Tripped breaker should be open", "OpenState", aspect.getCurrentState()); aspect.attemptReset(); assertEquals("Attempt Reset breaker should be closed", "HalfOpenState", aspect.getCurrentState()); aspect.reset(); assertEquals("Reset breaker should be closed", "ClosedState", aspect.getCurrentState()); } @Test public void testFailureCountResetsWhenBreakerDoes() throws Throwable { int threshold = 3; aspect.setFailureThreshold(threshold); expect(mockPjp.proceed()).andThrow(new RuntimeException()).times(threshold); replay(mockPjp); for(int i = 0; i < threshold; i++) { try { aspect.applyCircuitBreaker(mockPjp); assertEquals("Wrong error count", i, aspect.getCurrentFailureCount()); } catch(Throwable e) // NOPMD { // the first several calls should fail } } aspect.reset(); assertEquals("Error count didn't reset", 0, aspect.getCurrentFailureCount()); } }