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

如何测试Spring JPA的CriteriaBuilder.isFalse()方法?

Spring JPA Specification单元测试类型不兼容问题

问题描述

我有一个自定义方法,可从自定义对象创建Spring JPA的Specification,用于通过CriteriaBuilder.isFalse()方法检查属性是否为false。我使用Mockito编写了如下单元测试:

void unitTestIsNotDeleted() {
    Root root = mock(Root.class);
    CriteriaBuilder cb = mock(CriteriaBuilder.class);
    Predicate predicate = mock(Predicate.class);
    Path<Object> path = mock(Path.class);

    when(root.get("deleted")).thenReturn(path);
    when(cb.isFalse(path)).thenReturn(predicate);

    Specification<MyObj> specification = CustomSpecifications.isNotDeleted();
    Assertions.assertNotNull(specification);
    Predicate result = specification.toPredicate(root, query, cb);

    Assertions.assertNotNull(result);
    Assertions.assertEquals(predicate, result);
}

运行测试时出现如下异常:

java: incompatible types: jakarta.persistence.criteria.Path<java.lang.Object> cannot be converted to jakarta.persistence.criteria.Expression<java.lang.Boolean>

我曾用类似方式测试CriteriaBuilder.equals()未出现该异常。尝试通过中间Path<Boolean>转换Path<Object>,但因Mock误用出现PotentialStubbingProblem;使用Mockito.lenient()后测试可运行但结果失败,求解决办法。

解决办法

1. 直接匹配正确的泛型类型

CriteriaBuilder.isFalse()的参数要求是Expression<Boolean>,而Path<Boolean>是它的子类型,所以直接mockPath<Boolean>而非Path<Object>,从根源避免类型转换问题:

void unitTestIsNotDeleted() {
    Root<MyObj> root = mock(Root.class);
    CriteriaBuilder cb = mock(CriteriaBuilder.class);
    Predicate predicate = mock(Predicate.class);
    // 直接创建Path<Boolean>的mock对象
    Path<Boolean> path = mock(Path.class);

    when(root.get("deleted")).thenReturn(path);
    // 此时参数类型完全匹配,不会触发类型转换异常
    when(cb.isFalse(path)).thenReturn(predicate);

    Specification<MyObj> specification = CustomSpecifications.isNotDeleted();
    Assertions.assertNotNull(specification);
    // 测试中query参数未被使用,可传null
    Predicate result = specification.toPredicate(root, null, cb);

    Assertions.assertNotNull(result);
    Assertions.assertEquals(predicate, result);
}

2. 解决PotentialStubbingProblem

这个异常通常是因为mock对象的方法调用和你预先stub的逻辑不匹配,解决思路:

  • 确保root.get("deleted")的调用参数和stub时完全一致(比如字符串"deleted"不能写错)
  • 不要对同一个方法调用重复stub,避免逻辑冲突
  • 如果确实需要宽松的stub规则,使用lenient()时要保证stub的逻辑和实际调用一致:
lenient().when(root.get("deleted")).thenReturn(path);

3. 验证Specification的实际逻辑

确保你的CustomSpecifications.isNotDeleted()逻辑正确,比如:

public static Specification<MyObj> isNotDeleted() {
    return (root, query, cb) -> cb.isFalse(root.get("deleted"));
}

只有实际逻辑和测试中的stub对应上,才能保证断言结果匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:57:20