com.clicktravel.cheddar.application.retry.RetryableAspectTest.java Source code

Java tutorial

Introduction

Here is the source code for com.clicktravel.cheddar.application.retry.RetryableAspectTest.java

Source

/*
 * Copyright 2014 Click Travel Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.clicktravel.cheddar.application.retry;

import static com.clicktravel.common.random.Randoms.randomIntInRange;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.joda.time.DateTime;
import org.junit.Test;

@SuppressWarnings("unchecked")
public class RetryableAspectTest {

    @Test
    public void shouldConstructRetryableAspect() {
        // Given
        RetryableAspect retryableAspect = null;

        // When
        retryableAspect = new RetryableAspect();

        // Then
        assertNotNull(retryableAspect);
    }

    @Test
    public void shouldAttemptMethodAndNotRetry_withExceptionThrownAndRetryDisabled() throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final Retryable mockRetryable = setupSimpleRetryableMock();

        RetryableConfiguration.setRetryableEnabled(false);
        when(mockProceedingJoinPoint.proceed()).thenThrow(new RetryAspectTestException());

        // When
        RetryAspectTestException actualException = null;
        try {
            retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);
        } catch (final RetryAspectTestException e) {
            actualException = e;
        }

        // Then
        verify(mockProceedingJoinPoint).proceed();

        assertNotNull(actualException);
    }

    @Test
    public void shouldAttemptMethodAndNotRetry_withNoExceptionThrownAndRetryEnabled() throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final Retryable mockRetryable = setupSimpleRetryableMock();

        RetryableConfiguration.setRetryableEnabled(true);

        // When
        Exception actualException = null;
        try {
            retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);
        } catch (final RetryAspectTestException e) {
            actualException = e;
        }

        // Then
        verify(mockProceedingJoinPoint).proceed();

        assertNull(actualException);
    }

    @Test
    public void shouldAttemptMethodAndRetry_withExceptionThrownAndRetryEnabledAndMaxAttemptsAdjusted()
            throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final int maxAttempts = randomIntInRange(2, 4);
        final Retryable mockRetryable = setupMockRetryable(maxAttempts, 2000);

        RetryableConfiguration.setRetryableEnabled(true);
        when(mockProceedingJoinPoint.proceed()).thenThrow(new RetryAspectTestException());

        // When
        RetryAspectTestException actualException = null;
        try {
            retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);
        } catch (final RetryAspectTestException e) {
            actualException = e;
        }

        // Then
        verify(mockProceedingJoinPoint, times(maxAttempts)).proceed();

        assertNotNull(actualException);
    }

    @Test
    public void shouldAttemptMethodAndFailImmedaitelyOnException_withImmediateFailureExceptionClassThrownAndRetryEnabled()
            throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final int maxAttempts = 3;
        final Retryable mockRetryable = setupMockRetryable(maxAttempts, 2000);

        RetryableConfiguration.setRetryableEnabled(true);
        when(mockRetryable.failImmediatelyOn()).thenReturn(new Class[] { RetryAspectTestException.class });
        when(mockProceedingJoinPoint.proceed()).thenThrow(new RetryAspectTestException());

        // When
        RetryAspectTestException actualException = null;
        try {
            retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);
        } catch (final RetryAspectTestException e) {
            actualException = e;
        }

        // Then
        verify(mockProceedingJoinPoint, atMost(1)).proceed();

        assertNotNull(actualException);
    }

    @Test
    public void shouldAttemptMethodAndRetry_withExceptionThrownAndRetryEnabledAndDelayAdjusted() throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final long EPSILON_MS = 300; // test timing tolerance
        final int retryDelayInMillis = 400;
        final int maxAttempts = randomIntInRange(2, 6);
        final int expectedRuntime = retryDelayInMillis * (maxAttempts - 1);
        final Retryable mockRetryable = setupMockRetryable(maxAttempts, retryDelayInMillis);

        RetryableConfiguration.setRetryableEnabled(true);
        when(mockProceedingJoinPoint.proceed()).thenThrow(new RetryAspectTestException());

        // When
        DateTime startDateTime = null;
        RetryAspectTestException actualException = null;
        try {
            startDateTime = DateTime.now();
            retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);
        } catch (final RetryAspectTestException e) {
            actualException = e;
        }

        // Then
        verify(mockProceedingJoinPoint, times(maxAttempts)).proceed();

        assertNotNull(actualException);
        final long elapsedMillis = DateTime.now().getMillis() - startDateTime.getMillis();
        assertTrue(Math.abs(elapsedMillis - expectedRuntime) < EPSILON_MS);
    }

    @Test
    public void shouldAttemptMethodAndCallExceptionHandler_withExceptionThrownAndExceptionHandlerNamed()
            throws Throwable {
        // Given
        final RetryableAspect retryableAspect = new RetryableAspect();
        final ProceedingJoinPoint mockProceedingJoinPoint = setupSimpleProceedingJoinPointMock();
        final Retryable mockRetryable = setupSimpleRetryableMock();
        final RetryExceptionHandler retryExceptionHandler = new RetryExceptionHandler();
        final MethodSignature mockMethodSignature = mock(MethodSignature.class);
        final String exceptionHandlerName = "joinPointMethodExceptionHandlingMethod";

        RetryableConfiguration.setRetryableEnabled(false);
        when(mockProceedingJoinPoint.proceed()).thenThrow(new RetryAspectTestException());
        when(mockProceedingJoinPoint.getThis()).thenReturn(retryExceptionHandler);
        when(mockProceedingJoinPoint.getSignature()).thenReturn(mockMethodSignature);
        when(mockProceedingJoinPoint.getArgs()).thenReturn(new Object[] { "exampleArgument" });
        when(mockMethodSignature.getParameterTypes()).thenReturn(
                RetryExceptionHandler.class.getDeclaredMethod("joinPointMethod", String.class).getParameterTypes());
        when(mockRetryable.exceptionHandlers()).thenReturn(new String[] { exceptionHandlerName });

        // When
        retryableAspect.attemptMethodAndRetryIfNeeded(mockProceedingJoinPoint, mockRetryable);

        // Then
        verify(mockProceedingJoinPoint).proceed();

        assertTrue(retryExceptionHandler.exceptionHandlerBeenCalled);
    }

    private Retryable setupMockRetryable(final int maxAttempts, final int retryDelayInMillis) {
        final Retryable mockRetryable = mock(Retryable.class);
        when(mockRetryable.maxAttempts()).thenReturn(maxAttempts);
        when(mockRetryable.failImmediatelyOn()).thenReturn(new Class[0]);
        when(mockRetryable.exceptionHandlers()).thenReturn(new String[0]);
        when(mockRetryable.retryDelayMillis()).thenReturn(retryDelayInMillis);
        return mockRetryable;
    }

    private Retryable setupSimpleRetryableMock() {
        return setupMockRetryable(5, 2000);
    }

    private ProceedingJoinPoint setupSimpleProceedingJoinPointMock() {
        final ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
        final MethodSignature mockMethodSignature = mock(MethodSignature.class);

        when(mockProceedingJoinPoint.getThis()).thenReturn(mockProceedingJoinPoint);
        when(mockProceedingJoinPoint.getSignature()).thenReturn(mockMethodSignature);
        when(mockMethodSignature.getParameterTypes()).thenReturn(new Class[0]);
        return mockProceedingJoinPoint;
    }

    private class RetryAspectTestException extends Exception {
        private static final long serialVersionUID = 1L;
    }

    @SuppressWarnings("unused")
    private class RetryExceptionHandler {

        private boolean exceptionHandlerBeenCalled = false;

        public void joinPointMethod(final String firstParameter) {

        }

        public void joinPointMethodExceptionHandlingMethod(final RetryAspectTestException retryAspectTestException,
                final String firstParameter) {
            exceptionHandlerBeenCalled = true;
        }

    }

}