Bài 14: Kiểm thử (Testing) AOP trong Spring Boot

hoangkimhoangkim
4 min read

Mục tiêu bài học

  • Hiểu các thách thức khi kiểm thử AOP: Aspect “ẩn” trong proxy, phải test cả method gốc và advice.

  • Biết cách kiểm thử logic gốc vs. logic aspect: Phân biệt giữa unit test và integration test.

  • Thực hiện unit test, integration test cho AOP: Dùng Mockito, SpringBootTest để kiểm tra aspect có hoạt động đúng.

  • Debug proxy, verify advice chạy đúng: Cách kiểm tra xem Aspect có thực sự kích hoạt.


Nội dung chính

1. Khó khăn khi test AOP

  • Aspect không dễ thấy: Không thể gọi trực tiếp method của aspect mà phải test gián tiếp qua class bị AOP quản lý.

  • Pointcut có thể không match: Nếu pointcut không khớp, aspect sẽ không chạy => cần kiểm tra kỹ.

  • Proxy-based AOP ảnh hưởng đến test:

    • Spring AOP tạo proxy cho bean => khó test nếu dùng @Mock.

    • Nếu method gọi chính nó (self-invocation), AOP sẽ không chạy.

Ví dụ lỗi thường gặp khi test AOP:

MethodNotInterceptedException: Method was not intercepted by Aspect

\=> Nguyên nhân: Test không chạy trên Spring context, dẫn đến aspect không hoạt động.


2. Unit Test vs. Integration Test trong AOP

Loại testMục đíchƯu điểmNhược điểm
Unit TestKiểm tra logic bên trong aspectNhanh, không cần khởi động SpringKhông kiểm tra toàn bộ flow AOP
Integration TestKiểm tra AOP chạy đúng trên ứng dụng thực tếKiểm tra toàn bộ hệ thốngChạy chậm hơn

3. Unit Test Aspect (Dùng Mockito)

  • Mock JoinPoint để kiểm tra logic aspect hoạt động đúng.

3.1. Viết Aspect cần test

Aspect đo thời gian thực thi method:

@Aspect
@Component
public class PerformanceAspect {

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

    @Around("@annotation(com.example.PerformanceMonitor)")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long elapsedTime = System.currentTimeMillis() - startTime;
        logger.info("Execution time of {}: {} ms", joinPoint.getSignature(), elapsedTime);
        return result;
    }
}

3.2. Viết Unit Test với Mockito

  • Mock ProceedingJoinPoint để giả lập gọi method thực tế.
@ExtendWith(MockitoExtension.class)
class PerformanceAspectTest {

    @InjectMocks
    private PerformanceAspect performanceAspect;

    @Mock
    private ProceedingJoinPoint joinPoint;

    @Test
    void testMeasureExecutionTime() throws Throwable {
        when(joinPoint.proceed()).thenReturn("Success");
        when(joinPoint.getSignature()).thenReturn(() -> "mockMethod()");

        Object result = performanceAspect.measureExecutionTime(joinPoint);

        assertEquals("Success", result);
        verify(joinPoint, times(1)).proceed();
    }
}

📌 Giải thích:

  • Mock ProceedingJoinPoint để giả lập việc gọi method thực tế.

  • Gọi aspect measureExecutionTime và kiểm tra:

    • joinPoint.proceed() có được gọi đúng số lần không?

    • Kết quả trả về có đúng không?


4. Integration Test AOP (SpringBootTest)

  • Chạy Spring context để kiểm tra aspect thực sự hoạt động.

4.1. Viết Annotation @PerformanceMonitor

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

4.2. Viết Service để kiểm tra Aspect

@Service
public class DemoService {
    @PerformanceMonitor
    public String process() {
        return "Processing...";
    }
}

4.3. Viết Integration Test

@SpringBootTest
@AutoConfigureMockMvc
class PerformanceAspectIT {

    @Autowired
    private DemoService demoService;

    @Test
    void testPerformanceAspect() {
        String result = demoService.process();
        assertEquals("Processing...", result);
        // Kiểm tra log có đúng không (có thể dùng LogCaptor)
    }
}

📌 Giải thích:

  • Khởi chạy Spring context (@SpringBootTest) để đảm bảo AOP thực sự chạy.

  • Gọi method demoService.process() và kiểm tra kết quả có đúng không.

  • Có thể kiểm tra log bằng LogCaptor hoặc AssertJ.


5. Debugging Aspect

  • Bật log debug để kiểm tra proxy và pointcut
logging.level.org.springframework.aop=DEBUG
  • Kiểm tra proxy đang dùng (CGLIB hay JDK Dynamic Proxy)
System.out.println(AopProxyUtils.ultimateTargetClass(demoService));
  • Kiểm tra method có bị AOP bắt hay không
System.out.println(AopUtils.isAopProxy(demoService)); // True nếu có proxy

6. Best Practices for Testing AOP

Viết test cho cả unit và integration

  • Unit Test: Kiểm tra logic bên trong aspect.

  • Integration Test: Kiểm tra xem aspect có thực sự chạy trong Spring context không.

Kiểm tra log hoặc hành vi bị ảnh hưởng bởi AOP

  • Dùng LogCaptor để kiểm tra log nếu cần.

  • Ghi assertion trên giá trị trả về nếu aspect thay đổi kết quả method.

Tránh mock toàn bộ Spring bean trong Integration Test

  • Dùng @SpringBootTest thay vì mock bean bị AOP quản lý.

Đảm bảo Pointcut chính xác

  • Dùng debug hoặc log để kiểm tra aspect có thực sự chạy không.

Tóm tắt

  • Unit test aspect bằng Mockito: Giả lập JoinPoint, kiểm tra logic bên trong aspect.

  • Integration test để kiểm tra aspect chạy thực tế: Dùng @SpringBootTest, gọi method có AOP.

  • Debugging Aspect: Bật log debug, kiểm tra proxy, validate pointcut.

  • Best practices: Viết test cho cả unit và integration, đảm bảo AOP chạy đúng.

Sang Bài 15: Tổng kết, Best Practices, Pitfalls của Spring AOP.


Câu hỏi thảo luận

  1. Khi nào nên mock aspect thay vì chạy thực?

    • Nếu aspect không ảnh hưởng đến logic chính nhưng có thể làm chậm test.
  2. Bài học kinh nghiệm test AOP trong production?

    • Debug pointcut kỹ, kiểm tra overhead do AOP gây ra.
0
Subscribe to my newsletter

Read articles from hoangkim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

hoangkim
hoangkim