Integration tests for AspectJ

Let us use the same sample code as in my answer to the related AspectJ unit testing question:

Java class to be targeted by aspect:

package de.scrum_master.app;

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

Aspect under test:

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();
    }
}

You have several options, depending on what exactly you want to test:

  1. You can run the AspectJ compiler and verify its console output (with weaving info enabled) in order to make sure that the expected joinpoints are actually woven and others are not. But this would rather be a test for your AspectJ configuration and the build process as such than a real integration test.
  2. Similarly, you can create a new weaving classloader, load the aspect and then a few classes (load-time weaving, LTW) in order to dynamically check what gets woven and what does not. In this case you are testing if your pointcuts are correct rather than the integrated application consisting of core + aspect code.
  3. Last, but not least, you can perform a normal integration test, assuming how the application should behave after core + aspect code have been woven correctly. How to do this, depends on you concrete situation, specifically on what kind of side effect your aspect adds to the core code.

Subsequently I will describe option no. 3. Looking at the sample code above, we see the following side effects:

  • For small positive numbers, the aspect passes through the original parameter value to the intercepted method, the only side effect being additional log output.
  • For negative numbers, the aspect passes through the negated parameter value (e.g. turning -22 into 22) to the intercepted method, which is nicely testable.
  • For larger positive numbers, the aspect throws an exception, effectively stopping the original method from being executed at all.

Integration test for aspect:

package de.scrum_master.aspect;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.io.PrintStream;

import org.junit.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import de.scrum_master.app.Application;

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

    private Application application = new Application();

    private PrintStream originalSystemOut;
    @Mock private PrintStream fakeSystemOut;

    @Before
    public void setUp() throws Exception {
        originalSystemOut = System.out;
        System.setOut(fakeSystemOut);
    }

    @After
    public void tearDown() throws Exception {
        System.setOut(originalSystemOut);
    }

    @Test
    public void testPositiveSmallNumber() throws Throwable {
        application.doSomething(11);
        verify(System.out, times(1)).println(matches("execution.*doSomething.* 11"));
        verify(System.out, times(1)).println(matches("Doing something with number 11"));
    }

    @Test
    public void testNegativeNumber() throws Throwable {
        application.doSomething(-22);
        verify(System.out, times(1)).println(matches("execution.*doSomething.* -22"));
        verify(System.out, times(1)).println(matches("Doing something with number 22"));
    }

    @Test(expected = RuntimeException.class)
    public void testPositiveLargeNumber() throws Throwable {
        try {
            application.doSomething(333);
        }
        catch (Exception e) {
            verify(System.out, times(1)).println(matches("execution.*doSomething.* 333"));
            verify(System.out, times(0)).println(matches("Doing something with number"));
            assertEquals("oops", e.getMessage());
            throw e;
        }
    }
}

Et voilĂ , we are testing exactly the three types of side effects our sample aspect has by inspecting log output to a mock instance of System.out and by making sure that the expected exception is thrown for larger positive numbers.

Leave a Comment