0

I have defined an Aspect and it will be used when the method is annotated. Please see the sample code below

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PredefinedCheck {

}

@Aspect
@Component
public class PredefinedAspect {
    
    @Before("@annotation(PredefinedCheck)")
    @SneakyThrows
    public void check(JoinPoint joinPoint) {

        ......
        log.debug("hello!!");
    }
}

@Service
public class ActionService {
     
     @PredefinedCheck
     public MyEntity updateMyEntity(AuthenticationJwtToken authToken, EntityUpdateRequest request) {
          ......
     }
}

Now, the question is how can I unit test my PredefinedAspect code? I thought unit testing the updateMyEntity method will trigger it, but it didn't (I debugged and it not hit the break point. Also, the sonarqube doesn't shows the code being covered). Please advise.

Laodao
  • 1,547
  • 3
  • 17
  • 39
  • You need to run this as a Spring-Test so that it loads the context, otherwise that Aspect will never fire. did you check this link? https://stackoverflow.com/questions/41389015/junit-tests-for-aspectj – Dan Jan 24 '23 at 20:27
  • What you want is not a unit test, but an integration test. For a unit test, you should not need any DI container or Spring test magic. Besides, you cannot "unit-test an aspect annotation", you should rephrase your question title. An annotation does absolutely nothing, it just sits there. The aspect is what you want to test. – kriegaex Jan 25 '23 at 16:09
  • Two general answers I wrote about [how to unit-test an aspect](https://stackoverflow.com/a/41407336/1082681) and [how to integration-test an aspect](https://stackoverflow.com/a/41453156/1082681). You can use the former for both Spring AOP and native AspectJ. But the latter mostly applies to native AspectJ, because I use aspects outside of Spring. Anyway, the basic approach is the same, you would only use some kind of Spring testing infrastructure, like @Dan suggested. How to exactly test your aspect, depends on the details you are hiding from us behind "..." - not very helpful. – kriegaex Jan 25 '23 at 16:13

1 Answers1

0

Steps

  1. Create under src/test/ root project, SpringBootApplication class, that will start the context(loading/registering the AOP) for test purpose.
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackageClasses = { PredefinedAspect.class } )
public class ApplicationTest {
    public void main(String args[]) {
        SpringBootApplication.run(ApplicationTest.class, args);
    }
}
  1. Enable your test with SprintBootTest annotation, with the Application context above.
@SpringBootTest(classes = ApplicationTest.class)
  1. If your project enables some configuration, such Hibernate / Database and you aren't interested test it, exclude with EnableAutoConfiguration annotation. For example:
@EnableAutoConfiguration(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class,
        JpaRepositoriesAutoConfiguration.class
})

Full example:

import org.aspectj.lang.JoinPoint;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringRunner;

import javax.sql.DataSource;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;


@Service
class ActionServiceFake {
    @PredefinedCheck
    public MyEntity updateMyEntity(AuthenticationJwtToken authToken, EntityUpdateRequest request) {
    }
}


@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationTest.class)
@EnableAutoConfiguration(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class,
        JpaRepositoriesAutoConfiguration.class
})
public class PredefinedAspectTest {
    @SpyBean
    PredefinedAspect predefinedAspect;

    @Autowired
    ActionServiceFake actionServiceFake;

    @Test
    public void shouldCheckOneTimesBefore() {
        // exercise ...
        actionServiceFake.updateMyEntity(new AuthenticationJwtToken(), new EntityUpdateRequest());

        // expectation ...
        verify(predefinedAspect, times(1)).check(any(JoinPoint.class));
    }
}

You can use the external ActionService class, but, don't forget add it Application Context base classes list.

  • 1
    Nice answer. Like I said in my comment a few months ago, this is however an integration test (starting a full Spring Boot container), not a unit test. Both types of test make sense. My link to how to unit-test an aspect enables you to test the aspect logic as such in isolation without requiring any Spring magic or expensive containers. The unit test runs in milliseconds. – kriegaex Apr 28 '23 at 07:05
  • @kriegaex, I agree, you're absolutely right. Really this is an integration test and I would have emphasize about it. With regard to [how to unit-test an aspect](https://stackoverflow.com/a/41407336/1082681), I did like that. – Diogo Dio Pinto Apr 28 '23 at 15:42