6

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 {
Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
  • Can you provide your test class code here? – Sergey Bespalov Sep 22 '16 at 02:34
  • Hello @SergeyBespalov code updated, see `Alpha` section. Thanks! – Manuel Jordan Sep 22 '16 at 13:00
  • You can try to add `@EnableAspectJAutoProxy(proxyTargetClass=true)` annotation for your test classes. – Sergey Bespalov Sep 23 '16 at 04:32
  • Hello @SergeyBespalov, it does not work. Seems 'missing' something else – Manuel Jordan Sep 23 '16 at 12:42
  • what kind of weaving you are using? is it spring aop proxy? – Sergey Bespalov Sep 25 '16 at 16:21
  • AspectJ version `1.8.9` with `org.aspect:aspectjrt` and `org.aspect:aspectjweaver` that's all. An by default internally `spring-aop` is used, therefore I did not declare explicitly. – Manuel Jordan Sep 25 '16 at 18:30
  • I have a `@Configuration` class with `@EnableAspectJAutoProxy`. That's all. According with http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#aop-proxying it says `If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created` – Manuel Jordan Sep 25 '16 at 18:33
  • Do you think the following scenario http://stackoverflow.com/questions/39694633/spring-mvc-and-aop-pointcuts-for-controllers-only-works-in-testing-and-not-fo is related with the current problem? – Manuel Jordan Sep 26 '16 at 04:29
  • Yes, it can be related. – Sergey Bespalov Sep 26 '16 at 05:48

2 Answers2

10

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)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • Thanks so much by the valuable explanation Sam. I will do a research about your suggestions. About `JUnit instantiates your test class. Thus, Spring is not involved and therefore cannot apply AOP advice to the test instance`. Seems I was wrong about the idea that when I see a `JUnit` class with `@ContextConfiguration` Spring is involved in a 100%. I am confused with that paragraph. Pls with the best intentions try to expand a little more the idea for that. – Manuel Jordan Sep 26 '16 at 17:27
  • 3
    It is always the runner that instantiates your test class. So, if you're using the `Parameterized` runner, it is that runner which instantiates your test class (not Spring). If you are using the `SpringJUnit4ClassRunner`, you might _think_ Spring instantiates your test class; however, the `SpringJUnit4ClassRunner` actually extends JUnit's `BlockJUnit4ClassRunner`, and it is the `BlockJUnit4ClassRunner` that physically instantiates the test instance (again not Spring). – Sam Brannen Sep 30 '16 at 14:12
  • Thank you Sam. But remember I had the same problem even with `@RunWith(SpringJUnit4ClassRunner.class)` – Manuel Jordan Sep 30 '16 at 14:15
  • I edited my comment to address the `SpringJUnit4ClassRunner` as well. – Sam Brannen Sep 30 '16 at 14:16
  • Specifically, it is this method in which JUnit 4 instantiates your test class: `org.junit.runners.BlockJUnit4ClassRunner.createTest()` – Sam Brannen Sep 30 '16 at 14:17
  • Thanks again by your valuable support Sam! Much better now the understanding – Manuel Jordan Sep 30 '16 at 15:07
1

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"/>
Sergey Bespalov
  • 1,746
  • 1
  • 12
  • 29
  • Very interesting, wondered what could be the equivalent for `Gradle`. Let me do a research. – Manuel Jordan Sep 26 '16 at 12:14
  • Hello Sergey, consider the Sam's answer. Thanks a lot by your valuable support too. – Manuel Jordan Sep 26 '16 at 17:30
  • 1
    Sam's answer explanes the problem, but it is strange why Test instance is not part of ApplicationContext. I thing this can be a bug. I would advise you to try AspectJ compiler, it would solve this and other of your AOP problems that you asked on the site. I use it in all of my projects, and can say that it is good. – Sergey Bespalov Sep 27 '16 at 02:09
  • Yes, `but it is strange why Test instance is not part of ApplicationContext` I was confused too, but after to do an analysis: I think is not a bug, it because even when you see the Test class with a set of Spring's annotations, it does not contain a `@Configuration`, `@Component`, `@Service` etc. Therefore it is neither never created nor handled by Spring. It is created by `JUnit` and then some beans are 'available' thanks to the current annotations declared in the Test class. I accomplish my approach through Sam's suggestion. – Manuel Jordan Sep 27 '16 at 12:39
  • ` I would advise you to try AspectJ compiler, it would solve this and other of your AOP problems that you asked on the site` let's see. But I've confirmed that one of them is a kind of bug, I already wrote you a comment in the other Post about a `JIRA`. Not sure, but could you give a hand here http://stackoverflow.com/questions/39708410/spring-mvc-and-aop-pointcuts-only-apply-for-rest-controllers-and-not-for-commo thanks – Manuel Jordan Sep 27 '16 at 12:48
  • @ManuelJordan did you manage to solve this issue with gradle? – YanetP1988 Aug 19 '20 at 03:02