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

Spock Mock失效:测试抛出NullPointerException问题排查

Spock Mock测试触发NullPointerException问题

在使用Spock进行Mock测试时,systemTasksRepository.findByUserIdAndByTaskCode调用返回null,导致systemTaskOptional变量为null,最终触发空指针异常。以下是相关代码及排查方案:


测试代码

def "should get system tasks and check if done - mark as success" () {
    given:
        systemTasksRepository.findByUserIdAndDoneFalse(_) >> getSystemTasksAll()
        userApiService.getById(_) >> UserSnapshot.builder().storeAuthorities(Set.of(StoreAuthoritySnapshot.builder().role(StoreRole.OWNER).build())).companyAuthority(CompanyAuthoritySnapshot.builder().companyId(1L).build()).build()
        systemTasksRepository.findByUserId(_) >> getSystemTasksAll()
        systemTasksRepository.findByUserIdAndByTaskCode(_,_) >> Optional.of(SystemTask.builder().build())
    when:
        List<SystemTaskSnapshot> snapshots = tasksService.getSystemTasksAndCheck(1L)
    then:
        snapshots.size() == 2
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_STORE)
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_COMPANY)
}

触发异常的业务逻辑

if (notDoneSystemTasks.contains(SystemTaskTypes.CREATE_COMPANY)) {
    CompanyAuthoritySnapshot authoritySnapshot = userApiService.getById(userId).getCompanyAuthority();
    if (Objects.nonNull(authoritySnapshot) && Objects.nonNull(authoritySnapshot.getCompanyId())) {
        // 此处返回null,导致后续调用ifPresent时抛出NPE
        Optional<SystemTask> systemTaskOptional = systemTasksRepository.findByUserIdAndByTaskCode(userId, SystemTaskTypes.CREATE_COMPANY);
        systemTaskOptional.ifPresent(systemTask -> systemTask.setDone(true));
    }
}

服务Mock定义

private TasksMapper tasksMapper = Spy()
private SystemTasksRepository systemTasksRepository = Mock()
private UserApiService userApiService = Mock()

@Subject
private TasksService tasksService = new RepositoryTasksService(tasksMapper, systemTasksRepository, userApiService)

堆栈跟踪

Connected to the target VM, address: '127.0.0.1:51363', transport: 'socket'
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/Users/developer/.m2/repository/org/codehaus/groovy/groovy/2.5.8/groovy-2.5.8.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

java.lang.NullPointerException
    at com.nvt.pupil.tasks.service.service.RepositoryTasksService.checkIfDone(RepositoryTasksService.java:53)
    at com.nvt.pupil.tasks.service.service.RepositoryTasksService.getSystemTasksAndCheck(RepositoryTasksService.java:38)
    at com.nvt.pupil.tasks.service.service.RepositoryTasksServiceSpec.should get system tasks and check if done - mark as success(RepositoryTasksServiceSpec.groovy:65)

问题根因

Spock的Mock规则中,then块内的方法调用次数验证会覆盖given块中对同一方法的Stub返回值设置。你在given中给findByUserIdAndByTaskCode设置了返回Optional.of(...),但then里的1 * ...声明会重新定义这些调用的行为,且未指定返回值,导致Mock默认返回null,最终触发NPE。

修复方案

将返回值设置与调用次数验证合并,在then块中为每个调用指定返回值:

def "should get system tasks and check if done - mark as success" () {
    given:
        systemTasksRepository.findByUserIdAndDoneFalse(_) >> getSystemTasksAll()
        userApiService.getById(_) >> UserSnapshot.builder().storeAuthorities(Set.of(StoreAuthoritySnapshot.builder().role(StoreRole.OWNER).build())).companyAuthority(CompanyAuthoritySnapshot.builder().companyId(1L).build()).build()
        systemTasksRepository.findByUserId(_) >> getSystemTasksAll()
    when:
        List<SystemTaskSnapshot> snapshots = tasksService.getSystemTasksAndCheck(1L)
    then:
        snapshots.size() == 2
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_STORE) >> Optional.of(SystemTask.builder().build())
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_COMPANY) >> Optional.of(SystemTask.builder().build())
}

或者,保留given中的Stub设置,同时确保参数匹配足够精准,避免被then的验证覆盖:

def "should get system tasks and check if done - mark as success" () {
    given:
        systemTasksRepository.findByUserIdAndDoneFalse(_) >> getSystemTasksAll()
        userApiService.getById(_) >> UserSnapshot.builder().storeAuthorities(Set.of(StoreAuthoritySnapshot.builder().role(StoreRole.OWNER).build())).companyAuthority(CompanyAuthoritySnapshot.builder().companyId(1L).build()).build()
        systemTasksRepository.findByUserId(_) >> getSystemTasksAll()
        // 精准匹配测试中会传入的userId参数
        systemTasksRepository.findByUserIdAndByTaskCode(1L, _) >> Optional.of(SystemTask.builder().build())
    when:
        List<SystemTaskSnapshot> snapshots = tasksService.getSystemTasksAndCheck(1L)
    then:
        snapshots.size() == 2
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_STORE)
        1 * systemTasksRepository.findByUserIdAndByTaskCode(1L, RepositoryTasksService.SystemTaskTypes.CREATE_COMPANY)
}

两种方案都能保证findByUserIdAndByTaskCode调用返回非null的Optional,避免空指针异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:34:59