Spock单元测试StepVerifier.expectError传任意类型均通过问题
问题根因
两个核心问题导致测试断言完全未生效,出现任意类型参数都能通过的假阳性结果:
- 最核心的错误:
StepVerifier的断言链没有调用终止方法verify()。
StepVerifier的执行逻辑是:所有expectXxx方法只是在组装校验规则,不会触发任何实际的订阅和校验动作,只有调用最终的verify()(或者verifyTimeout、verifyThenAssertThat这类终止方法)时,才会真正订阅被测的Mono/Flux,按顺序执行所有预设的断言。你现在只写了.expectError(String.class)就终止了代码,等于只是创建了一个StepVerifier实例,没有跑任何校验,这就是你遇到任意类型入参(哪怕是String.class)都能通过测试的直接原因。 - Spock测试代码存在顺序错误与潜在逻辑缺陷:
- given块变量初始化顺序颠倒:你先执行
new PaymentsBO(contractServiceMock),之后才初始化contractServiceMock = Mock(ContractClient),构造被测类时传入的mock实际是null,后续调用会直接触发空指针; - 被测业务代码存在NPE风险:
onErrorResume逻辑中直接调用throwable.getCause(),如果抛出的异常实例没有设置cause(比如你测试中构造的无参new ContractServiceException()),这里会直接抛出NullPointerException,最终返回的错误信号是NPE,不是你预期的CustomException。
- given块变量初始化顺序颠倒:你先执行
修正方案
首先调整单元测试代码,修复初始化顺序问题,补充verify()调用:
def "Returns an error if the Contract Service returns a non-200"() { given: // 先初始化Mock对象,再注入到被测类 def contractServiceMock = Mock(ContractClient) def testSubject = new PaymentsBO(contractServiceMock) when: def result = testSubject.updatePaymentInfo(record) then: // Mock交互校验放在then块,Spock会自动在when执行前完成桩注入 1 * contractServiceMock.getContract(CONTRACT_UUID) >> Mono.error(new ContractServiceException()) // StepVerifier必须调用verify()触发校验,传入预期的异常类型 StepVerifier.create(result) .expectError(CustomException.class) .verify() }
其次修复业务代码中的空指针风险,调整异常拼接逻辑:
@Service @RequiredArgsConstructor public class PaymentsBO { private final ContractClient contractClient; public Mono<Void> updatePaymentInfo(Request record) { return contractClient .getContract(UUID.fromString(record.getContractUuid())) .onErrorResume(throwable -> Mono.error(() -> new CustomException( "Contract Service responded with a non-200 due to " + Optional.ofNullable(throwable.getCause()).orElse(throwable).getMessage()))) .flatMap( // happy-path logic ); } }
内容的提问来源于stack exchange,提问作者spartanhooah
相关产品推荐
相关产品推荐

