Actually, there are two things that are interesting from the testing perspective an an aspect:
- The business logic that the aspect executes when triggered
- The pointcut expression(s) which trigger aspect execution
Even the first of them is not so easy to test because you need an instance of ProceedingJoinPoint which is cumbersome to implement or mock (and it is not recommended to mock external interfaces, as it is explained in Growing Object-Oriented Software, Guided by Tests, for example).
The solution
Let's imagine that we have an aspect that must throw an exception if a method's first argument is null, otherwise allow the method invocation proceed.
It should only be applied to controllers annotated with our custom @ThrowOnNullFirstArg annotation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Aspect public class ThrowOnNullFirstArgAspect { @Pointcut("" + "within(@org.springframework.stereotype.Controller *) || " + "within(@(@org.springframework.stereotype.Controller *) *)") private void isController() {} @Around("isController()") public Object executeAroundController(ProceedingJoinPoint point) throws Throwable { throwIfNullFirstArgIsPassed(point); return point.proceed(); } private void throwIfNullFirstArgIsPassed(ProceedingJoinPoint point) { if (!(point.getSignature() instanceof MethodSignature)) { return; } if (point.getArgs().length > 0 && point.getArgs()[0] == null) { throw new IllegalStateException("The first argument is not allowed to be null"); } } } |
We could test it like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class ThrowOnNullFirstArgAspectTest { private final ThrowOnNullFirstArgAspect aspect = new ThrowOnNullFirstArgAspect(); private TestController controllerProxy; @Before public void setUp() { AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(new TestController()); aspectJProxyFactory.addAspect(aspect); DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory(); AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory); controllerProxy = (TestController) aopProxy.getProxy(); } @Test public void whenInvokingWithNullFirstArg_thenExceptionShouldBeThrown() { try { controllerProxy.someMethod(null); fail("An exception should be thrown"); } catch (IllegalStateException e) { assertThat(e.getMessage(), is("The first argument is not allowed to be null")); } } @Test public void whenInvokingWithNonNullFirstArg_thenNothingShouldBeThrown() { String result = controllerProxy.someMethod(Descriptor.builder().externalId("id").build()); assertThat(result, is("ok")); } @Controller @ThrowOnNullFirstArg private static class TestController { @SuppressWarnings("unused") String someMethod(Descriptor descriptor) { return "ok"; } } } |
The key part is inside the setUp() method. Please note that it also allows to verify the correctness of your pointcut expression, so it solves both problems.
Of course, in a real project it is better to extract the 'proxy contstruction' code to some helper class to avoid code duplication and make the intentions clearer.