Тесты JUnit для AspectJ

Я пытаюсь написать тесты Junit для Custom Aspect. Вот фрагмент класса Aspect:

@Aspect
@Component
public class SampleAspect {

    private static Logger log = LoggerFactory.getLogger(SampleAspect.class);

    @Around("execution(* org.springframework.data.mongodb.core.MongoOperations.*(..)) || execution(* org.springframework.web.client.RestOperations.*(..))")
    public Object intercept(final ProceedingJoinPoint point) throws Throwable {
        logger.info("invoked Cutom aspect");
         return point.proceed();

    }

}

Таким образом, вышеупомянутый аспект перехватывает всякий раз, когда точка соединения совпадает с pointcut. Прекрасно работает.

Но мой вопрос в том, как unit test этот класс. У меня есть следующий тест Junit:

@Test(expected = MongoTimeoutException.class)
    public void TestWithMongoTemplate() {
        //MongoDocument class
        TestDocument test = new TestDocument();

        ApplicationContext ctx = new AnnotationConfigApplicationContext(TestMongoConfigurationMain.class);
        MongoTemplate mongoTemplate = ctx.getBean(MongoTemplate.class);

        //this call is being intercepted by SampleAspect
        mongoTemplate.save(test);

    }

Итак, мой mongoTemplate.save(test) в Junit перехвачен SampleAspect, поскольку он соответствует pointcut. Но как я должен убедиться в junits (возможно, утверждая), что мой SampleAspect перехватывает, когда эта точка соединения вызывается?

Я не могу утверждать на возвращаемое значение из intercept(), поскольку он не делает ничего особенного, кроме выполнения совместной точки. Таким образом, мой Junit не может найти разницы, выполняется ли его по аспектному или регулярному исполнению на основе возвращаемых значений.

Любые примеры фрагментов кода при тестировании аспектов были бы замечательными, если бы они предоставлены. Спасибо

Ответ 1

Я думаю, что вы пытаетесь протестировать это аспектное сотканение и сопоставление pointcut. Обратите внимание, что это будет интеграция, а не unit test. Если вы действительно хотите unit test логики вашего аспекта и потому, что в любом случае вы отметили вопрос "mockito", я предлагаю вам сделать именно это: Напишите unit test и высмеируйте точку соединения аспекта и, возможно, ее другие параметры, если они есть. Вот несколько более сложный пример с некоторой внутриаспектной логикой:

Класс Java должен быть нацелен по аспекту:

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        new Application().doSomething(11);
        new Application().doSomething(-22);
        new Application().doSomething(333);
    }

    public void doSomething(int number) {
        System.out.println("Doing something with number " + number);
    }
}

Аспект под тестом:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class SampleAspect {
    @Around("execution(* doSomething(int)) && args(number)")
    public Object intercept(final ProceedingJoinPoint thisJoinPoint, int number) throws Throwable {
        System.out.println(thisJoinPoint + " -> " + number);
        if (number < 0)
            return thisJoinPoint.proceed(new Object[] { -number });
        if (number > 99)
            throw new RuntimeException("oops");
        return thisJoinPoint.proceed();
    }
}

Журнал консоли при запуске Application.main(..):

Как вы можете видеть, аспект проходит на 11, отрицает -22 и выдает исключение для 333:

execution(void de.scrum_master.app.Application.doSomething(int)) -> 11
Doing something with number 11
execution(void de.scrum_master.app.Application.doSomething(int)) -> -22
Doing something with number 22
execution(void de.scrum_master.app.Application.doSomething(int)) -> 333
Exception in thread "main" java.lang.RuntimeException: oops
    at de.scrum_master.aspect.SampleAspect.intercept(SampleAspect.aj:15)
    at de.scrum_master.app.Application.doSomething(Application.java:10)
    at de.scrum_master.app.Application.main(Application.java:7)

Unit test для аспект:

Теперь мы действительно хотим убедиться, что аспект делает то, что он должен, и охватывает все пути выполнения:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import static org.mockito.Mockito.*;

public class SampleAspectTest {
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    private ProceedingJoinPoint proceedingJoinPoint;

    private SampleAspect sampleAspect = new SampleAspect();

    @Test
    public void testPositiveSmallNumber() throws Throwable {
        sampleAspect.intercept(proceedingJoinPoint, 11);
        // 'proceed()' is called exactly once
        verify(proceedingJoinPoint, times(1)).proceed();
        // 'proceed(Object[])' is never called
        verify(proceedingJoinPoint, never()).proceed(null);
    }

    @Test
    public void testNegativeNumber() throws Throwable {
        sampleAspect.intercept(proceedingJoinPoint, -22);
        // 'proceed()' is never called
        verify(proceedingJoinPoint, never()).proceed();
        // 'proceed(Object[])' is called exactly once
        verify(proceedingJoinPoint, times(1)).proceed(new Object[] { 22 });
    }

    @Test(expected = RuntimeException.class)
    public void testPositiveLargeNumber() throws Throwable {
        sampleAspect.intercept(proceedingJoinPoint, 333);
    }
}

Теперь запустите этот простой тест JUnit + Mockito, чтобы проверить изолированную логику аспект, а не логику проводки/ткачества. Для последнего вам понадобится другой тип теста.

P.S.: Только для вас я использовал JUnit и Mockito. Обычно я просто использую Spock и его встроенные издевательские возможности. ;-)

Ответ 2

@Dchris: Приведенный выше ответ от kriegaex дает подробное объяснение того, как имитировать ткачество аспекта, но если вы планируете иметь некоторые пользовательские значения дляенеурия, возможно, вы можете попробовать вот так

    ProceedingJoinPoint pjp;
    MethodSignature methodSig;

    @Before
    public void beforeEachTest() {
        // Use this to create all mocks annotated and inject it into utils
        MockitoAnnotations.initMocks(this);
        model = new LoggingModel();
        model.setPhase("test phase");
        model.setUser(null);
        model.setMethodName("customTestMethod");
        model.setClassName("ServiceUtilsTest");

        // mocking pjp
        pjp = Mockito.mock(ProceedingJoinPoint.class);
        methodSig = Mockito.mock(MethodSignature.class);

    }


....
....
 private Method testMethod() throws NoSuchMethodException {
        return getClass().getDeclaredMethod("customTestMethod");
    }
 public void customTestMethod() {

    }

Примечание. Чтобы это работало, вы должны удалить аннотированные @Mocks и использовать ручную насмешку, как описано выше. Если вы аннотируете его, вы получите разумные значения по умолчанию

Надеюсь это поможет!!