Mockito.when()设置桩时触发已有Mock逻辑抛出NPE问题
问题说明
这是Mockito使用中的常见问题,测试配置存在逻辑缺陷。
触发NPE的核心原因:
Mockito.when(mock.method(matcher))语法在设置新桩时,会真实执行一次mock.method(matcher)调用,用于捕获方法签名和匹配规则。- 你在
@BeforeEach阶段已经通过Mockito.any()匹配器为addAddress方法注册了全局桩,any()会匹配所有入参,包括设置新桩时传入的匹配器代理对象。 - 执行测试方法内的桩设置代码时,全局桩的
thenAnswer逻辑被触发,此时传入的参数是Mockito内部的匹配器代理,不是真实构造的Address对象,调用getValues()返回null,最终触发空指针。
解决方案
你可以选择以下任意一种方案修复,推荐组合使用方案1和方案4:
- 方案1:给全局桩的Answer逻辑增加空值判断,避免直接调用null对象的方法
private void mockAddressClient() { Mockito .when(addressClient.addAddress(Mockito.any())) .thenAnswer( in -> { de.vbg.suvi.api.address.v1.model.Address adr = in.getArgument(0); de.vbg.suvi.api.address.v1.model.ValueAddress values = adr.getValues(); // 新增空值判断 if (values == null) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } AddressResponse addressResponse = new AddressResponse() .id(UUID.randomUUID()) .streetLine1(values.getStreetLine1()) .streetLine2(values.getStreetLine2()) .streetNumber(values.getStreetNumber()) .zipCode(values.getZipCode()) .city(values.getCity()) .country(values.getCountry()); addresses.put(addressResponse.getId(), addressResponse); return new ResponseEntity<>(addressResponse, HttpStatus.CREATED); }); } - 方案2:将测试方法内的桩设置语法从
when().thenThrow()替换为doThrow().when(),do系列语法不会在设置桩时触发已有桩的逻辑// 替换原测试方法内的桩设置代码 Mockito.doThrow( new ValidationException( List.of( new SuviFieldValidationError( ViolationCode.INVALID, "message", "streetNumber")))) .when(addressClient) .addAddress(Mockito.argThat(a -> a.getPlaceId().equals(placeId))); - 方案3:给
@BeforeEach内的全局桩添加lenient()宽松模式修饰,避免桩设置阶段的调用触发旧桩逻辑// 修改全局桩写法 Mockito.lenient().when(addressClient.addAddress(Mockito.any())) // 后续thenAnswer逻辑不变 - 方案4:移除
@BeforeEach内的全局通用桩,每个测试方法按需设置自己需要的桩规则,从根源上避免桩冲突。
注意:Mockito桩匹配遵循「后注册的桩优先匹配」规则,如果你保留全局桩,测试方法内注册的特殊桩在实际执行业务逻辑时会优先生效,不会被全局桩覆盖,不需要担心逻辑冲突。
内容的提问来源于stack exchange,提问作者Moritz Schmitz v. Hülst
相关产品推荐
相关产品推荐

