JUnit 5中嵌套映射测试出现NullPointerException问题排查
报错信息
java.lang.NullPointerException: Cannot invoke "com.fincons.mapper.EmployeeMapper.mapEmployeeToEmployeeDtoWithoutObjects(com.fincons.entity.Employee)" because "this.employeeMapper" is null
at com.fincons.mapper.DepartmentMapper.lambda$mapDepartmentToDepartmentDto$0(DepartmentMapper.java:26)
问题根源
DepartmentMapper中依赖了EmployeeMapper,但测试代码仅创建了DepartmentMapper的Spy实例,未将EmployeeMapper注入其中,导致调用mapDepartmentToDepartmentDto方法时this.employeeMapper为null。同时测试类中存在冗余操作:既用@InjectMocks注解,又在@BeforeEach里手动实例化departmentService,造成依赖注入混乱。
解决方案
方案1:给DepartmentMapper添加构造注入
先修改DepartmentMapper,通过构造方法接收EmployeeMapper:
public class DepartmentMapper { private final EmployeeMapper employeeMapper; // 构造方法注入依赖 public DepartmentMapper(EmployeeMapper employeeMapper) { this.employeeMapper = employeeMapper; } public DepartmentDTO mapDepartmentToDepartmentDto(Department department) { if (department.getEmployees() != null) { List<EmployeeDTO> employeeDTOList = new ArrayList<>(); department.getEmployees().forEach(employee -> employeeDTOList.add(employeeMapper.mapEmployeeToEmployeeDtoWithoutObjects(employee)) ); return new DepartmentDTO( department.getId(), department.getName(), department.getAddress(), department.getCity(), employeeDTOList); } // 补充空列表场景的返回逻辑 return new DepartmentDTO(department.getId(), department.getName(), department.getAddress(), department.getCity(), null); } }
调整测试类,移除冗余的手动初始化代码,让Mockito自动处理依赖:
@ExtendWith(MockitoExtension.class) public class DepartmentServiceTest { @InjectMocks private DepartmentServiceImpl departmentService; @Mock private DepartmentRepository departmentRepository; @Spy private EmployeeMapper employeeMapper; // 构造DepartmentMapper Spy时传入依赖的EmployeeMapper @Spy private DepartmentMapper departmentMapper = new DepartmentMapper(employeeMapper); @BeforeEach void init() { // 移除以下冗余代码: // departmentRepository = mock(DepartmentRepository.class); // departmentService = new DepartmentServiceImpl(departmentRepository, departmentMapper, employeeMapper); } @Test void testGetDepartmentByIdExist() { long departmentId = 3L; Department existingDepartment = new Department(3L, "name", "address", "city"); Position position = new Position(3L, "Web Developer", 1500.0); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); LocalDate date1 = LocalDate.parse("1942-03-10", formatter); LocalDate date2 = LocalDate.parse("1942-03-10", formatter); LocalDate date3 = LocalDate.parse("1942-03-10", formatter); Employee employee = new Employee(5L, "Carlo", "Verdone", "carlo.verdone@gmail.com" , "Male", date1, date2, date3 , existingDepartment, position); List<Employee> newListEmployee = new ArrayList<>(); newListEmployee.add(employee); existingDepartment.setEmployees(newListEmployee); // 修正:JPA的findById默认返回Optional,匹配方法签名 when(departmentRepository.findById(departmentId)).thenReturn(Optional.of(existingDepartment)); ResponseEntity<Object> response = departmentService.getDepartmentById(departmentId); assertTrue(response.getStatusCode().is2xxSuccessful()); } }
方案2:将Mapper声明为Spring组件,用SpyBean管理
如果Mapper是Spring管理的组件(添加@Component注解),可借助@SpyBean让Spring容器处理依赖注入:
修改DepartmentMapper:
@Component public class DepartmentMapper { @Autowired private EmployeeMapper employeeMapper; // 原map方法逻辑不变 }
修改测试类:
@SpringBootTest @ExtendWith(MockitoExtension.class) public class DepartmentServiceTest { @InjectMocks private DepartmentServiceImpl departmentService; @Mock private DepartmentRepository departmentRepository; @SpyBean private DepartmentMapper departmentMapper; @SpyBean private EmployeeMapper employeeMapper; @BeforeEach void init() { // 移除所有手动初始化代码 } // 测试方法同方案1 }
核心逻辑总结
两种方案的核心都是确保DepartmentMapper实例能获取到有效的EmployeeMapper实例:要么通过构造方法显式传递依赖,要么借助Spring容器完成注入,避免依赖对象为null。
内容的提问来源于stack exchange,提问作者Lorenzo Gaetani

