Spring中BDDMockito测试JPA分页查询时空指针异常的排查与解决
分析Mock测试中NullPointerException的原因及测试用例编写规范
异常原因分析
你碰到的NullPointerException本质是Mockito参数匹配不匹配导致的:
- 在测试代码里,你给
dao.findByDriverId预设的调用预期是:given(this.dao.findByDriverId(1L, PageRequest.of(0, 2))).willReturn(journeyPage); - 但实际在
JourneyService的getJourneyOfUser方法中,调用dao时传入的Pageable是PageRequest.of(0,10)(因为你测试调用service时传的length参数是10)。
Mockito默认的参数匹配规则是严格的对象相等校验,PageRequest的equals方法会比较page、size等属性,这里size一个是2、一个是10,显然不相等,所以mock的dao方法不会返回你预设的journeyPage,而是返回null。当service里调用journeyPage.getContent()时,自然就抛出了NPE。
测试用例编写规范
针对这类Mockito测试场景,分享几个关键的编写规范:
1. 用参数匹配器替代硬编码参数
当不需要严格匹配某个具体Pageable对象时,使用Mockito的参数匹配器来灵活匹配:
// 匹配任意Long类型的driverId,任意Pageable对象 given(this.dao.findByDriverId(anyLong(), any(Pageable.class))).willReturn(journeyPage); // 或者更精准:只匹配driverId为1L的情况,Pageable任意 given(this.dao.findByDriverId(eq(1L), any(Pageable.class))).willReturn(journeyPage);
这样不管service传入的PageRequest参数是什么,只要符合匹配器规则,都会返回预设结果。
2. 保持测试参数与mock预期一致
如果确实需要严格匹配参数,确保测试调用service的参数和mock时的参数完全一致:
// 测试调用时传入和mock一致的length=2 ResponseDto responseDto = this.journeyService.getJourneyOfUser(1L, 0, 2);
3. 添加mock方法调用验证
在测试结尾添加验证步骤,确认mock的dao方法是否被正确调用,能快速排查参数不匹配问题:
// 验证dao.findByDriverId被调用1次,参数是1L和PageRequest.of(0,10) verify(this.dao, times(1)).findByDriverId(1L, PageRequest.of(0,10));
如果参数不匹配,Mockito会抛出Wanted but not invoked类的异常,帮你快速定位问题点。
4. 确保Mock注解生效
测试类要正确使用Mockito的注解驱动:
- JUnit4:在测试类上添加
@RunWith(MockitoJUnitRunner.class) - JUnit5:在测试类上添加
@ExtendWith(MockitoExtension.class)
否则@Mock和@InjectMocks可能无法生效,导致dao实例为null(虽然这次不是这个问题,但这是基础规范)。
5. 统一管理测试参数
对于PageRequest这类可通过参数构建的对象,用变量统一管理,避免硬编码错误:
int testPage = 0; int testSize = 10; Pageable testPageable = PageRequest.of(testPage, testSize); // mock时使用统一变量 given(this.dao.findByDriverId(1L, testPageable)).willReturn(journeyPage); // 调用service时也用同样的变量 ResponseDto responseDto = this.journeyService.getJourneyOfUser(1L, testPage, testSize);
这样能从根源上避免参数不一致的问题。
内容的提问来源于stack exchange,提问作者nirvair
相关产品推荐
相关产品推荐

