Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring AOP - @Pointcut: @Before advice for @Test methods does not work

I am working with:

  • Spring Framework 4.3.2
  • AspectJ 1.8.9
  • JUnit
  • Gradle

The project is based in multi-modules.

In src/main/java (main) I have some @Aspect classes and they work how is expected. I can confirm it through Runtime and Testing

Now I need for JUnit through logging show the @Test method name that is executed

Therefore in src/test/java (test) I have the following:

class TestPointcut {

    @Pointcut("execution(@org.junit.Test * *())")                         
    public void testPointcut(){}

}

@Aspect
@Component
public class TestAspect {

    private static final Logger logger = LoggerFactory.getLogger(TestAspect.class.getSimpleName());

    @Before(value="TestPointcut.testPointcut()")
    public void beforeAdviceTest(JoinPoint joinPoint){
        logger.info("beforeAdviceTest - Test: {} - @Test: {}", joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName() );
    }

}

Observe the second class has @Aspect and @Component therefore it is recognized by Spring

Note: I can confirm that If I write wrong the @Pointcut syntax or expression I get errors.

The problem is when I execute my @Test methods, For the TestAspect class the @Before advice never works.

I did a research in Google and I have seen that the @Pointcut("execution(@org.junit.Test * *())") pattern is correct. Even If I use a more explicit such as: @Pointcut(value="execution(public void com.manuel.jordan.controller.persona.*Test.*Test())"), it does not work.

Consider I have the following for Gradle

project(':web-27-rest') {
    description 'Web - Rest'
    dependencies {
       compile project(':web-27-service-api')

       testRuntime project(':web-27-aop')
       testRuntime project(':web-27-aop').sourceSets.test.output

What is missing or wrong?

Alpha:

One kind of Test classes are:

  • Server side working with @Parameters and @ClassRule + @Rule

Therefore:

@RunWith(Parameterized.class)
@ContextConfiguration(classes={RootApplicationContext.class})
@Transactional
public class PersonaServiceImplTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE= new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    private PersonaService personaServiceImpl;

    ...

    @Parameters
    public static Collection<Persona[]> data() {
     .....
        });
    }

    ...

    @Test
    @Sql(scripts={"classpath:....-script.sql"})
    public void saveOneTest(){
    ....
    }

Other are:

  • Web side working with (@WebAppConfiguration) and either:
    • with @Parameters and @ClassRule + @Rule
    • without @Parameters and @ClassRule + @Rule

Therefore (below the second approach):

@Transactional
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={RootApplicationContext.class, ServletApplicationContext.class})
public class PersonaDeleteOneControllerTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    private ResultActions resultActions;

    ...

    @BeforeClass
    public static void setUp_(){
      ...
    }

    @Before
    public void setUp(){
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void deleteOneHtmlGetTest() throws Exception {
like image 644
Manuel Jordan Avatar asked Dec 24 '22 01:12

Manuel Jordan


2 Answers

JUnit instantiates your test class. Thus, Spring is not involved and therefore cannot apply AOP advice to the test instance.

As was mentioned by Sergey Bespalov, the only way to have AspectJ advice applied to your test instance is to use compile-time or load-time weaving. Note that this would not be configured within Spring. Spring can be used to configure AOP for Spring-managed beans, but the test instance is managed by the testing framework (i.e., JUnit 4 in your scenario).

For tests using the Spring TestContext Framework, however, I would not recommend using AspectJ. Instead, the best solution is to implement a custom TestExecutionListener that performs the logging. You could then register that TestExecutionListener explicitly via @TestExecutionListeners or have it picked up automatically for your entire suite. For the latter, see the discussion on automatic discovery in the Testing chapter of the Spring reference manual.

Regards,

Sam (author of the Spring TestContext Framework)

like image 113
Sam Brannen Avatar answered Dec 28 '22 06:12

Sam Brannen


You can use AspectJ Compile or Load time weaving as alternative of spring-aop proxying. In such approach you will not depend on spring context complicated logic to apply advices in your code. Aspect code will be just inlined during compilation or class loading phase. Example below shows how to enable AspectJ Compile Time Weaving:

pom.xml

This Maven configuration enables AspectJ compiler that makes bytecode post processing of your classes.

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
    </dependency>
</dependencies>
<plugins>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.6</version>
        <configuration>
            <showWeaveInfo>true</showWeaveInfo>
            <source>${java.source}</source>
            <target>${java.target}</target>
            <complianceLevel>${java.target}</complianceLevel>
            <encoding>UTF-8</encoding>
            <verbose>false</verbose>
            <XnoInline>false</XnoInline>
        </configuration>
        <executions>
            <execution>
                <id>aspectj-compile</id>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
            <execution>
                <id>aspectj-compile-test</id>
                <goals>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>

applicationContext.xml

Also you may need to add aspect instance to Spring Application Context for dependency injection.

<bean class="TestAspect" factory-method="aspectOf"/>
like image 30
Sergey Bespalov Avatar answered Dec 28 '22 08:12

Sergey Bespalov