How to mock an aspect

老子叫甜甜 提交于 2019-12-10 14:34:05

问题


I am currently working on some monitoring tool using aspectj. Because this tool should be technology independent (as far as possible), I am not using Spring for injection. But I want my aspects to be unit-tested.

Aspect example:

@Aspect
public class ClassLoadAspect {
    private Repository repository;

    public ClassLoadAspect() {
        repository = OwlApiRepository.getInstance();
    }  

    @After("anyStaticInitialization()")
    public void processStaticInitilization(JoinPoint jp) {
        Class type = jp.getSourceLocation().getWithinType();
        if (type.isInterface()) {
            repository.storeInterfaceInitialization(type);
        } else if (type.isEnum()) {
            repository.storeEnumInitialization(type);
        } else {
            repository.storeClassInitialization(type);
        }

    }

    @Pointcut("staticinitialization(*) && !within(cz.cvut.kbss.odra..*)")
    public void anyStaticInitialization() {
    }

    public Repository getRepository() {
        return repository;
    }

    public void setRepository(Repository repository) {
        this.repository = repository;
    }  
}

However, I really dont know, how to construct unit test (the repository field should be mocked (using mockito)), But I do not have aspect creation under control, hence I cannot set the dependency manually. What should I call to get the instance? Or there is some other scenario how to unit-test aspectj aspects.

Thanks.


回答1:


You say you find your own way of introducing the mock object hacky. What exactly do you dislike and how do you imagine it to be? I can only guess:

Do you dislike the fact that you globally replace calls to OwlApiRepository.getInstance() in your meta aspect? Then you could specifically restrict mock object injection to the aspect's constructor (I am using native AspectJ syntax because I feel uncomfortable with the POJO annotation style):

public privileged aspect ClassLoadTestAspect {
    static boolean active = true;

    declare precedence : ClassLoadTestAspect, ClassLoadAspect;
    pointcut classLoadAspect() :
        if(active) && 
        withincode(ClassLoadAspect.new()) && 
        call(* OwlApiRepository.getInstance());

    Object around() : classLoadAspect() {
        return new MockRepository();
    }
}

As you can also see, this variant of a meta (aspect-testing) aspect also has a switch to turn it on and off at will. Maybe this was also something you disliked. As I said, I am guessing. After your feedback I might be able to answer more specifically.

Edit: As for your concerns, I think I have addressed them as far as possible:

  • You do not need a mock holder.

  • The aspect can be (de)activated. It would be easy to make its activation dependent on other conditions, so it is only active in your test environment. If this is still not enough, use compile-time weaving for your production aspects and load-time weaving for your test aspect. This way its byte code will not even be present in your production environment.

  • My version does not globally replace anything, but like a good surgeon only cuts in a minimally invasive way exactly at one place.

  • I cannot really understand your concern about byte code manipulation for several reasons: You use AspectJ, i.e. inherently byte code manipulation (weaving). You use Mockito which creates classes at runtime. I also do not understand where you see a deficiency in AspectJ. You have not explained how you want the "standard means of the language" to behave or what interface it should provide for testing. Even if you had, I cannot change the language (AJ) and tool (Mockito) of your own choice for you.




回答2:


you can split your tests. first test the logic of the aspect. it's a pojo. you can test it however you wants. second part is testing the pointcuts. in this case create another, simple aspect with same pointcuts (e.g. extract them as a constant). maybe there are some dedicated testing tools but i'm not aware of any and it was the easiest way that come to my mind




回答3:


My current solution is introducing this AspectJ hack in order to override the singletons factory method

@Aspect
public class MockingAspect {

    @Around("call(synchronized static OwlApiRepository *(..))")
    public OwlApiRepository processGetInstance(ProceedingJoinPoint jp) {      
        System.out.println("getting mock");
        return MockHolder.getMock();
    }
}



回答4:


You just want unit testing right? This is a small unit test for testing a custom annotation with aspects with the purpose of wrapping a throwable in a custom application exception. (Testng + Mockito)

public class ResourceApplicationExceptionAspectTest {
@Mock
private ProceedingJoinPoint pjp;
@Mock
private ResourceApplicationException resourceApplicationException; //annotation definition

@BeforeMethod
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

}

@Test(groups ="unit", expectedExceptions = ResourceApplicationException.class)
public void testWrapExceptionAdvice() throws Throwable {

    ResourceApplicationExceptionAspect aspect = new ResourceApplicationExceptionAspect();

    when(pjp.proceed()).thenThrow(new NullPointerException());
    aspect.wrapExceptionAdvice(pjp, resourceApplicationException);
}



回答5:


How about something along these lines, basically continue to keep your aspect, inside aspect delegate the behavior to another interface, and mock that interface for your tests, instead of mocking the aspect itself. Here is a pseudocode:

public interface ClassLoadHelper{
    void processStaticInitialization(Class<?> clazz);
}

public class ClassLoadHelperImpl implements ClassLoadHelper{
    private Repository repository;

    public ClassLoadHelperImpl() {
        repository = OwlApiRepository.getInstance();
    }  

    void processStaticInitialization(Class<?> clazz){
        if (type.isInterface()) {
            this.repository.storeInterfaceInitialization(type);
        } else if (type.isEnum()) {
            this.repository.storeEnumInitialization(type);
        } else {
            this.repository.storeClassInitialization(type);
        }        
    }
}


@Aspect
public class ClassLoadAspect {
    private ClassLoadHelper classLoadHelper;


    @After("anyStaticInitialization()")
    public void processStaticInitilization(JoinPoint jp) {
        Class<?> type = jp.getSourceLocation().getWithinType();
        this.classLoadHelper.processStaticInitialization(type);

    }

    @Pointcut("staticinitialization(*) && !within(cz.cvut.kbss.odra..*)")
    public void anyStaticInitialization() {
    }

    public ClassLoadHelper getClassLoadHelper() {
        return classLoadHelper;
    }

    public void setClassLoadHelper(ClassLoadHelper classLoadHelper) {
        this.classLoadHelper = classLoadHelper;
    }  
}

Now in your test you can do this:

ClassLoadAspect.aspectOf().setClassLoadHelper(mockClassLoadHelper);


来源:https://stackoverflow.com/questions/11825325/how-to-mock-an-aspect

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!