Spring单元测试用Mockito获取实体嵌套对象报空指针如何解决
Spring Service层Mockito单元测试空指针问题修复
问题复现
待测试的Service层业务逻辑包含多层实体关联调用,代码片段如下:
VirtualDatacenterModel vdc = vdcRepository.findById(vmDTO.getVdc()).orElseThrow(() -> new ClientException("Invalid VDC id")); DataCenterModel dc = vdc.getDatacenter(); String vmName = vdc.getTenant().getName() + "_[" + vmDTO.getName() + "]";
使用Mockito编写的测试代码运行时抛出NullPointerException,原测试代码:
CreateVmDTO vmDTO = Mockito.mock(CreateVmDTO.class); VmModel vm = Mockito.mock(VmModel.class); VirtualDatacenterModel vdc = Mockito.mock(VirtualDatacenterModel.class, Mockito.RETURNS_DEEP_STUBS); TenantModel tenant = Mockito.mock(TenantModel.class); Mockito.when(vmRepository.save(vm)).thenReturn(new VmModel()); Mockito.when(vdcRepository.findById(vmDTO.getVdc())).thenReturn(Optional.of(new VirtualDatacenterModel())); Mockito.doReturn(tenant).when(vdc).getTenant(); Mockito.when(vdc.getTenant().getName()).thenReturn("Olivia"); VmModel vmReturn = vmService.createVM(vmDTO);
错误原因
空指针由核心Mock配置错误导致:
- 手动Mock并配置了关联关系的
vdc对象没有被业务代码实际调用:vdcRepository.findById()的存根返回的是新new出来的VirtualDatacenterModel真实实例,这个实例没有设置任何关联属性,getTenant()默认返回null,后续调用getName()直接触发空指针。 vmRepository.save()的存根参数匹配错误:存根指定只有传入手动Mock的vm实例时才会触发返回值,但业务逻辑中传给save方法的是Service内部构造的VmModel对象,和定义的Mock实例不相等,该存根根本不会生效。- 给
vdc设置RETURNS_DEEP_STUBS后又手动配置getTenant()的返回值,配置逻辑本身存在冲突,属于冗余写法。
正确写法
核心原则是:Repository层存根返回的对象,必须是提前配置好所有关联属性的实例(不管是Mock对象还是手动set属性的真实实体对象)。修正后的测试代码:
// 配置测试入参的存根,明确业务逻辑会用到的字段返回值 CreateVmDTO vmDTO = Mockito.mock(CreateVmDTO.class); Mockito.when(vmDTO.getVdc()).thenReturn(1L); // 替换为实际使用的VDC ID类型,比如Long/UUID Mockito.when(vmDTO.getName()).thenReturn("test-biz-vm"); // 逐层构造关联实体的Mock,配置需要用到的返回值 TenantModel tenant = Mockito.mock(TenantModel.class); Mockito.when(tenant.getName()).thenReturn("Olivia"); DataCenterModel dc = Mockito.mock(DataCenterModel.class); VirtualDatacenterModel vdc = Mockito.mock(VirtualDatacenterModel.class); Mockito.when(vdc.getTenant()).thenReturn(tenant); Mockito.when(vdc.getDatacenter()).thenReturn(dc); // 配置Repository层存根,返回上面配置好关联关系的vdc对象,不要new新的实例 Mockito.when(vdcRepository.findById(eq(vmDTO.getVdc()))).thenReturn(Optional.of(vdc)); // save方法用参数匹配器any()接收任意VmModel实例,直接返回入参即可,不需要绑定特定Mock对象 Mockito.when(vmRepository.save(any(VmModel.class))).thenAnswer(invocation -> invocation.getArgument(0)); // 执行业务方法 VmModel vmReturn = vmService.createVM(vmDTO); // 后续补充业务断言即可
优化建议:如果实体类逻辑简单,不需要全部用Mock,可以直接new真实实体,通过setter方法设置关联属性,这种写法更贴近真实运行场景,能避免很多Mock配置和实际逻辑脱节的问题。
内容的提问来源于stack exchange,提问作者pehc
相关产品推荐
相关产品推荐

