You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

新增Aspect单元测试引发Mockito相关测试失败问题排查求助

问题描述

为自定义Aspect新增单元测试后,批量运行所有测试时多数用例出现Mockito错误(未完成存根、空指针异常、Mock释放失败等),单独运行该测试正常,移除后所有测试恢复正常。已尝试统一spring-boot-starter-test与mockito-inline版本,未解决问题。

注解代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ValidCheck {
    boolean enabled() default true;
}

切面代码

@Aspect
@Component
@Slf4j
public class ValidCheckService {
    
    @Value("${field.to.validate}")
    private String field;

    @Around(value = "@annotation(com.test.logging.annotations.ValidCheck)")
    public ResponseEntity<?> validateFields(ProceedingJoinPoint pjp) throws Throwable {

        try {
            Object[] args = pjp.getArgs();
            var validCheck =
                    ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class);

            if (validCheck.enabled()) {

                if (args != null && args.length > 0) {
                    var someClass =
                            Arrays.stream(args)
                                    .filter(arg -> arg instanceof SomeClass)
                                    .findAny()
                                    .map(arg -> (SomeClass) arg)
                                    .orElseThrow(() -> new IllegalArgumentException("No SomeClass present"));

                    var fieldToCheck = someClass.getField();

                    if (!field.equalsIgnoreCase(fieldToCheck)) {
                        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
                    }
                }
            } else {
                log.info("Check disabled");
            }
        } catch (Exception e) {
            log.error("Exception in validating fields", e);
        }
        
        return (ResponseEntity<?>) pjp.proceed();
    }
}

测试类代码

class ValidCheckServiceTest {
    
    private ProceedingJoinPoint pjp;
    private SomeClass some;
    private ValidCheck validCheck;
    private ValidCheckService validCheckService;

    @BeforeEach
    public void init() {
        pjp = mock(ProceedingJoinPoint.class);
        some = mock(SomeClass.class);
        validCheck = mock(ValidCheck.class);
        MethodSignature methodSignature = mock(MethodSignature.class);
        Method method = mock(Method.class);

        validCheckService = new ValidCheckService();

        setField(validCheckService, "field", SOME_FIELD);

        when(pjp.getSignature()).thenReturn(methodSignature);
        when(methodSignature.getMethod()).thenReturn(method);
        when(method.getAnnotation(ValidCheck.class)).thenReturn(validCheck);
    }

    @Test
    void test1() throws Throwable {

        when(pjp.getArgs()).thenReturn(new Object[]{some, new Object()});
        when(((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class)).thenReturn(validCheck);
        when(validCheck.enabled()).thenReturn(true);
        when(some.getClientId()).thenReturn("value");

        var responseEntity = validCheckService.validateFields(pjp);

        assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode());
    }

    @Test
    void test2() throws Throwable {

        when(pjp.getArgs()).thenReturn(new Object[]{some, new Object()});
        when(((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class)).thenReturn(validCheck);
        when(validCheck.enabled()).thenReturn(true);
        when(some.getClientId()).thenReturn(SOME_FIELD);
        when(pjp.proceed()).thenReturn(ResponseEntity.ok().build());

        var responseEntity = validCheckService.validateFields(pjp);

        assertNotEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode());
    }

    @Test
    void test3() throws Throwable {

        when(pjp.getArgs()).thenReturn(new Object[]{some, new Object()});
        when(((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class)).thenReturn(validCheck);
        when(validCheck.enabled()).thenReturn(false);
        when(pjp.proceed()).thenReturn(ResponseEntity.ok().build());

        var responseEntity = validCheckService.validateFields(pjp);

        assertNotEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode());
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    void test4(boolean enabled) throws Throwable {

        when(pjp.getArgs()).thenReturn(new Object[]{new Object()});
        when(((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class)).thenReturn(validCheck);
        when(validCheck.enabled()).thenReturn(enabled);
        when(pjp.proceed()).thenReturn(ResponseEntity.ok().build());

        var responseEntity = validCheckService.validateFields(pjp);

        assertNotEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode());
    }


    private static final String SOME_FIELD = "field";
}

当前依赖版本

+- org.mockito:mockito-inline:jar:4.5.1:test
+- org.mockito:mockito-junit-jupiter:jar:4.5.1:test
+- org.mockito:mockito-core:jar:3.5.13:test
问题根源分析
  1. Mockito版本冲突:当前依赖中mockito-core为3.5.13,而mockito-inline和mockito-junit-jupiter是4.5.1,跨大版本的混合依赖会导致内部API不兼容,引发Mock对象初始化、存根逻辑异常,批量测试时类加载器复用这些不一致的类,问题暴露更明显。
  2. Aspect测试的Mock污染:测试中直接实例化ValidCheckService并通过反射设置字段,但该类是Spring管理的@Component,批量测试时Spring上下文可能复用这个实例,导致Mock对象残留到其他测试中,干扰正常Bean生命周期。
  3. 存根重复定义:测试方法中重复定义了when(((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(ValidCheck.class)).thenReturn(validCheck),而@BeforeEach已经完成相同存根,重复存根会导致Mockito内部状态混乱,批量运行时累积错误。
  4. 未清理Mock状态:测试类未使用@ExtendWith(MockitoExtension.class),Mockito无法自动在测试后重置Mock状态,批量运行时Mock的存根会在测试间泄漏。

内容的提问来源于stack exchange,提问作者Hindol Dey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 20:47:25