3

I have a custom validation annotation in a spring project.

The aim for annotation is to accept the parameter names that are supposed to be id and dept values.

The aim of the aspect is to fetch the paramNames from the annotation, find the corresponding parameter position in the method signature, get the values from the identified positions and perform validation with the values.

Here are the classes I have written so far.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ValidateDeptAnnotation {
    String id();
    String dept();
}

The aspect that does the validation when a method is annotated.

@Aspect
@Component
public class ValidateDeptAspect {

    @Before("@annotation(<package>.ValidateDeptAnnotation)")
    public void runValidation(JoinPoint joinPoint) throws MemberIdentityException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ValidateDeptAnnotation annotation = method.getAnnotation(ValidateDeptAnnotation.class); 
        String id = null;
        String dept = null;
        for (int index = 0; index < signature.getParameterNames().length; index++) {
            String paramterName = signature.getParameterNames()[index];
            if (annotation.dept().equals(paramterName)) {
                dept = joinPoint.getArgs()[index].toString();
            } else if (annotation.id().equals(paramterName)) {
                id = joinPoint.getArgs()[index].toString();
            }
        }
        //....further processing...throw appropriate error msgs logic
    }

}

The test class

@RunWith(PowerMockRunner.class)
@PrepareOnlyThisForTest({Method.class})
public class ValidateDeptAspectTestMockedMethod {
    @InjectMocks
    private ValidateDeptAspect validationAspect;
    @Mock
    private JoinPoint joinPoint;

    @Mock
    private MethodSignature methodSignature;
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void testIdParamNotFound() {
        String[] methodSignatureParams = {"id","dept"};
        String[] argumentValues= {"789","dept-789-pacman"};

        Mockito.doReturn(methodSignature).when(joinPoint).getSignature();
        Mockito.doReturn(methodSignatureParams).when(methodSignature).getParameterNames();
        Mockito.doReturn(argumentValues).when(joinPoint).getArgs();
        ValidateDeptAnnotation annotation = Mockito.mock(ValidateDeptAnnotation.class);

        Method mockedMethod = PowerMockito.mock(Method.class);
        Mockito.doReturn(mockedMethod).when(methodSignature).getMethod();
        PowerMockito.doReturn(annotation).when(mockedMethod).getAnnotation(Mockito.any());
        //        PowerMockito.when(mockedMethod.getAnnotation(ValidateDept.class)).thenReturn(annotation); --didnot work, same error.
        Mockito.doReturn("iiiiid").when(annotation).id();
        Mockito.doReturn("dept").when(annotation).dept();

        validationAspect.runValidation(joinPoint);


        ///...further assertion logic to check for error message as iiiid != id
        //but never gets here.

   }
}

When I run the test case, it fails with NullPointerException at the line in the Aspect.

if (annotation.dept().equals(paramterName))  

When I debug the test case, the annotation is obtained properly here.

PowerMockito.doReturn(annotation).when(mockedMethod).getAnnotation(Mockito.any());

However, the call to the aspect method throws NPE. Any help is much appreciated.

Thanks in advance.

Vijay Kalidindi
  • 165
  • 3
  • 12
  • If you need PowerMock in order to test your application, the design is wrong. Make your application testable and kick PowerMock. – kriegaex Mar 13 '17 at 23:08

3 Answers3

3

For those who wander into this situation. I haven't figured how to solve the problem or what the problem is.

I have settled for this approach currently - Create dummy methods in the test case that reflect your test case and proceed with test cases normally.

Code:

@RunWith(MockitoJUnitRunner.class)
public class ValidateDeptAspectTestMockedMethod {

    @InjectMocks
    private ValidateDeptAspect validationAspect;
    @Mock
    private JoinPoint joinPoint;

    @Mock
    private MethodSignature methodSignature;
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void testIdParamNotFound() {
        String[] methodSignatureParams = {"id","dept"};
        String[] argumentValues= {"789","dept-789-pacman"};

        Mockito.doReturn(methodSignature).when(joinPoint).getSignature();
        Mockito.doReturn(methodSignatureParams).when(methodSignature).getParameterNames();
        Mockito.doReturn(argumentValues).when(joinPoint).getArgs();

        Method method = myMethod("noMemberId");
        Mockito.doReturn(method).when(methodSignature).getMethod();

        validationAspect.runValidation(joinPoint);


        ///...further assertion logic to check for error message
        // The test is successful.
    }

    @ValidateDeptAnnotation(memberId = "",accessToken = "accessToken")
    private void noMemberId(String memberId, String accessToken) {}
}
Vijay Kalidindi
  • 165
  • 3
  • 12
1

I faced same situation when I try to write an unit test of an aspect. Finally, I find the problem is you can't mock the Method class. You can get your method through reflect:

Method method = TestClass.class.getDeclaredMethod("yourMethod");

And it can help you to get your Annotation. Hope it can help.

0

I'm currently in a similar situation but I believe testing the aspect is including the joinpoint trigger. I created fake class and validate the behaviour & trigger of my aspect. It makes the test much more comprehensive and also validates the correctness of the joinpoint.

2 remarks on code below:

  • I use spring, mainly because I always use AOP with Spring, but conceptually it would work without.
  • Proxy and AOP don't go well hand in hand. So caution is adviced with spy and mock.

T

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfig.class})
public class ValidateDeptAspectTestMockedMethod {

    @Autowired
    private SampleService sampleService;



    @Test
    public void testAllWell() {
        sampleService.noMemberId("123", "heroquest");
        // validation etc..
    }

    @Test
    public void testNopeDidntWork() {
        sampleService.noMemberId("123", "heroquest");
        // validation etc..
    }


    @EnableAspectJAutoProxy
    public static class TestConfig {

        @Bean
        public SampleService sampleService() {
            // WARNING - SPYING CAN NO LONGER TRIGGER ASPECT BECAUSE OF PROXY
            return new SampleService();
        }

    }


    public static class SampleService {

        @ValidateDeptAspect(id="789", dept ="dept-789-pacman") 
        public void noMemberId(String id, String dept) {
            // maybe some state you want to save to check after

        }
    }
}
Luc S.
  • 1,144
  • 10
  • 7