Reputation: 1545
I created a simple aspect in Spring using Spring Boot 2.1.6.RELEASE. It basically logs the total time spent on a method.
@Aspect
@Component
public class TimeLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(TimeLoggerAspect.class);
@Around("@annotation(demo.TimeLogger)")
public Object methodTimeLogger(ProceedingJoinPoint joinPoint)
throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.info("Method " + joinPoint.getSignature() + ": " + totalTime + "ms");
return proceed;
}
}
the aspect is triggered by a TimeLogger
annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimeLogger {
}
and is used in a component like this
@Component
public class DemoComponent {
@TimeLogger
public void sayHello() {
System.out.println("hello");
}
}
A spring boot demo application will invoke sayHello
via the run
method of the CommandLineRunner
interface.
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private DemoComponent demoComponent;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
demoComponent.sayHello();
}
}
For completeness, I add my modifications in build.gradle
: adding libraries for aop, spring test and jupiter (junit).
compile("org.springframework.boot:spring-boot-starter-aop")
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("org.junit.jupiter:junit-jupiter-api")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
Running the application will output (trimmed for readability)
hello
... TimeLoggerAspect : Method void demo.DemoComponent.sayHello(): 4ms
So far, so good. Now I create a test based on @SpringBootTest
annotation and jupiter.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {DemoComponent.class, TimeLoggerAspect.class})
public class DemoComponentFailTest {
@Autowired
private DemoComponent demoComponent;
@Test
public void shouldLogMethodTiming() {
demoComponent.sayHello();
}
}
and here I get the output
hello
No output from the TimeLoggerAspect
, since it seems it is not being triggered.
Is something missing to trigger the aspect in the test? Or are there other ways of testing the aspect in spring boot?
Upvotes: 20
Views: 26586
Reputation: 31
@Import (AnnotationAwareAspectJAutoProxyCreator.class)
This alone did not work for me.
I had to Include my aspect class and controller class in ContextConfiguration. And then Autowire them in the test class.
@ContextConfiguration (classes = {MyController.class, MyAspect.class})
@Autowired
MyController myController;
@Autowired
MyAspect myAspect;
Leaving it here. Thought this might help somebody someday.
Upvotes: 1
Reputation: 503
When I had to test an aspect, I used the approach below.
@SpringBootTest
@ContextConfiguration(classes = {MyAspectImpl.class, MyAspectTest.TestConfiguration.class})
@EnableAspectJAutoProxy
public class MyAspectTest {
@org.springframework.boot.test.context.TestConfiguration
static class TestConfiguration {
@Bean
public MyAspectTestClass myAspectTestClass() {
return new MyAspectTestClass();
}
}
@Autowired
private MyAspectTestClass target;
@Test
public void testCorrectlySetsPoolNameUsingMethodParameter() {
target.testMethod();
}
@NoArgsConstructor
private static class MyAspectTestClass {
@MyAspect
public void testMethod() {
//Add some logic here
}
}
}
Upvotes: 1
Reputation: 106
You have to put @EnableAspectJAutoProxy with your file @Configuration that declares the bean with @Aspect.
@Aspect
@Configuration
@EnableAspectJAutoProxy
public class TimeLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(TimeLoggerAspect.class);
@Around("@annotation(demo.TimeLogger)")
public Object methodTimeLogger(ProceedingJoinPoint joinPoint)
throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.info("Method " + joinPoint.getSignature() + ": " + totalTime + "ms");
return proceed;
}
}
I think that will do the work.
Upvotes: 9
Reputation: 5833
I had similar problem. My Aspect is listening on controller methods. To get it activated, importing the AnnotationAwareAspectJAutoProxyCreator
made the trick:
@RunWith(SpringRunner.class)
@Import(AnnotationAwareAspectJAutoProxyCreator.class) // activate aspect
@WebMvcTest(MyController.class)
public class MyControllerTest {
...
}
Upvotes: 21
Reputation: 1545
Another solution that seems to work is adding AnnotationAwareAspectJAutoProxyCreator
in classes
of @SpringBootTest
, although I am not quite certain why.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { DemoComponent.class,
TimeLoggerAspect.class,
AnnotationAwareAspectJAutoProxyCreator.class })
public class DemoComponentFailTest {
@Autowired
private DemoComponent demoComponent;
@Test
public void shouldLogMethodTiming() {
demoComponent.sayHello();
}
}
Upvotes: 6
Reputation: 1337
You need to start an @SpringBootApplication
. However, it does not have to be the one you use to start your app in production. It can be a special one for this test only and can be in your test sources root not your src.
@SpringBootApplication
@ComponentScan(basePackageClasses = {DemoComponent.class, TimeLoggerAspect.class})
public class SpringBootTestMain {
public static void main(String[] args) {
SpringApplication.run(SpringBootTestMain.class, args);
}
}
Then in your test this is the only class you need to list.
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringBootTestMain.class)
public class DemoComponentFailTest {
Upvotes: 5