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

Spring Boot中使用Specification编写JUnit测试遇空指针及参数匹配问题

问题解决思路与方案

核心问题分析

你的两个报错本质是同一个原因:Specification是Lambda表达式实现的,每次调用AdminUserSpecifications.hasOrganizationId()都会生成全新的实例。测试代码中手动创建的specification对象,和业务方法里生成的specification不是同一个实例,导致Mockito的参数匹配失败:

  • 初始测试时,匹配失败导致findAll()返回Mockito默认的null,触发空指针异常;
  • 改用any(Specification.class)后,若Pageable的匹配存在问题(比如多次调用toPageable()生成不同实例),也会出现参数不匹配。

具体解决方案

方案1:简化参数匹配(快速解决)

直接用any(Specification.class)匹配Specification参数,同时确保Pageable参数匹配(同一AdminUserListRequest对象调用toPageable()返回的实例通常是相等的,可用eq()匹配)。

修改测试代码的When和Then部分:

// When
Mockito.when(identity.getUserType()).thenReturn(userType);
Mockito.when(identity.getOrganizationId()).thenReturn(mockAdminUserEntity.getOrganizationId());
// 改用any匹配Specification,eq匹配Pageable
Mockito.when(adminUserRepository.findAll(
        Mockito.any(Specification.class), 
        Mockito.eq(mockAdminUserListRequest.toPageable())
)).thenReturn(mockPageAdminUserEntities);

Page<AdminUser> pageAdminUsers = adminUserService.getAdminUsers(mockAdminUserListRequest);

// Then
PageBuilder.assertEquals(mockPageAdminUsers, pageAdminUsers);
// 验证时使用相同的匹配器
Mockito.verify(adminUserRepository, Mockito.times(1))
        .findAll(Mockito.any(Specification.class), Mockito.eq(mockAdminUserListRequest.toPageable()));

方案2:自定义匹配器(严格验证逻辑)

如果需要验证Specification的逻辑是否正确(比如是否使用了预期的organizationId),可以自定义ArgumentMatcher来检查:

  1. 编写匹配器类:
class AdminUserSpecificationMatcher implements ArgumentMatcher<Specification<AdminUserEntity>> {
    private final String expectedOrgId;

    public AdminUserSpecificationMatcher(String expectedOrgId) {
        this.expectedOrgId = expectedOrgId;
    }

    @Override
    public boolean matches(Specification<AdminUserEntity> spec) {
        // 模拟JPA查询所需的对象
        Root<AdminUserEntity> root = Mockito.mock(Root.class);
        CriteriaQuery<?> query = Mockito.mock(CriteriaQuery.class);
        CriteriaBuilder cb = Mockito.mock(CriteriaBuilder.class);
        Path<Object> orgIdPath = Mockito.mock(Path.class);

        // 模拟路径获取
        Mockito.when(root.get("organizationId")).thenReturn(orgIdPath);
        // 模拟equal条件构建
        Mockito.when(cb.equal(orgIdPath, expectedOrgId)).thenReturn(Mockito.mock(Predicate.class));

        // 执行Specification的逻辑,验证是否生成有效Predicate
        Predicate predicate = spec.toPredicate(root, query, cb);
        return predicate != null;
    }
}
  1. 在测试中使用该匹配器:
// When
Mockito.when(adminUserRepository.findAll(
        Mockito.argThat(new AdminUserSpecificationMatcher(mockAdminUserEntity.getOrganizationId())),
        Mockito.eq(mockAdminUserListRequest.toPageable())
)).thenReturn(mockPageAdminUserEntities);

// Then
Mockito.verify(adminUserRepository, Mockito.times(1))
        .findAll(
                Mockito.argThat(new AdminUserSpecificationMatcher(mockAdminUserEntity.getOrganizationId())),
                Mockito.eq(mockAdminUserListRequest.toPageable())
        );

额外注意事项

  • 确保AdminUserListRequest.toPageable()方法返回的Pageable实例重写了equals和hashCode,否则eq()匹配会失败;
  • 如果业务中organizationId可能为null,需要补充空值场景测试,验证SearchSpecificationBuilder.eq()返回null时的逻辑。

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:13:12