使用Mockito模拟MapStruct Mapper时返回Null对象的问题排查
MapStruct Mapper单元测试返回Null问题
我使用MapStruct实现了类型转换Mapper,服务层通过构造注入该Mapper,编写JUnit单元测试时用Mockito的@Mock模拟Mapper实例。调试可见Mapper已被成功模拟,但进入服务层后,Mapper的转换方法返回Null对象。Spring Boot应用运行时该Mapper工作完全正常,仅单元测试出现此问题。
Mapper类
@Mapper(componentModel = "spring") public interface EmployeeHolidayMapper { @Mapping(target = "employeeEntity.empId",source = "employeeHolidayDto.empId") @Mapping(target = "leaveStartDate",expression = "java(LocalDate.parse(employeeHolidayDto" + ".getEmpLeaveStartDate().toString(),dateTimeFormatter))") @Mapping(target = "leaveEndDate",expression = "java(LocalDate.parse(employeeHolidayDto" + ".getEmpLeaveEndDate().toString(),dateTimeFormatter))") EmployeeHolidayEntity employeeHoliDayDtoToEmployeeHolidayEntity(EmployeeHolidayDto employeeHolidayDto,LocalDate localDate,DateTimeFormatter dateTimeFormatter); }
服务类
@Service @RequiredArgsConstructor public class EmployeeHolidayService { private final HolidayRepo holidayRepo; private final EmployeeHolidayRepo employeeHolidayRepo; private final EmployeeHolidayMapper employeeHolidayMapper; public Mono<Integer> saveEmployeeHolidayData(EmployeeHolidayDto employeeHolidayDto){ return Optional.ofNullable(employeeHolidayDto) .filter(dto->dto.getEmpLeaveEndDate()!=null && dto.getEmpLeaveStartDate() != null && dto.getEmpLeaveStartDate() > 0 && dto.getEmpLeaveEndDate() > 0 && dto.getEmpLeaveEndDate() .compareTo(dto.getEmpLeaveStartDate()) >=0) .map(dto->{ EmployeeHolidayEntity employeeHolidayEntity = employeeHolidayMapper .employeeHoliDayDtoToEmployeeHolidayEntity(dto, LocalDate.now(), DateTimeFormatter.BASIC_ISO_DATE); int diffOfDays = employeeHolidayEntity.getLeaveEndDate().getDayOfYear() - employeeHolidayEntity.getLeaveStartDate().getDayOfYear(); int noOfLeavesLeft = holidayRepo.getNoofLeavesLeft(dto.getEmpId()) - (diffOfDays); if (noOfLeavesLeft > 0) { int updatedRows = holidayRepo.updateNoLeaveDays(dto.getEmpId(), noOfLeavesLeft); if (updatedRows > 0) { EmployeeHolidayEntity employeeHolidayEntitySaved = employeeHolidayRepo .saveAndFlush(employeeHolidayEntity); if (employeeHolidayEntitySaved.getEmployeeHolidayId() > 0) { return Mono.just(updatedRows); } else { return Mono.just(0); } } else { return Mono.just(0); } } else { return Mono.just(0); } }) .orElse(null); } }
JUnit测试类(Mockito)
@ExtendWith(MockitoExtension.class) class HolidaymanagementApplicationTests { @Mock HolidayRepo holidayRepo; @Mock EmployeeHolidayRepo employeeHolidayRepo; @Mock EmployeeHolidayMapper employeeHolidayMapper; @InjectMocks EmployeeHolidayService employeeHolidayService; @Test void testPostrequest() { String startDate = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); String endDate = LocalDate.now().plusDays(2).format(DateTimeFormatter.BASIC_ISO_DATE); EmployeeHolidayDto employeeHolidayDto = new EmployeeHolidayDto(1, Integer.parseInt(startDate), Integer.parseInt(endDate)); EmployeeEntity employeeEntity = new EmployeeEntity(1, null, null, null, null); EmployeeHolidayEntity employeeHolidayEntity = new EmployeeHolidayEntity(1, LocalDate.parse(startDate, DateTimeFormatter.BASIC_ISO_DATE), LocalDate.parse(endDate, DateTimeFormatter.BASIC_ISO_DATE), employeeEntity); when(employeeHolidayMapper.employeeHoliDayDtoToEmployeeHolidayEntity(employeeHolidayDto, LocalDate.now(), DateTimeFormatter.BASIC_ISO_DATE)).thenReturn(employeeHolidayEntity); HolidayEntity holidayEntity = new HolidayEntity(1, 10, employeeEntity); when(holidayRepo.getNoofLeavesLeft(1)).thenReturn(10); when(holidayRepo.updateNoLeaveDays(1, 8)).thenReturn(1); EmployeeHolidayEntity employeeHolidayDbEntity = new EmployeeHolidayEntity(1, employeeHolidayEntity.getLeaveStartDate(), employeeHolidayEntity.getLeaveEndDate(), employeeEntity); when(employeeHolidayRepo.saveAndFlush(employeeHolidayEntity)).thenReturn(employeeHolidayDbEntity); assertEquals(employeeHolidayService.saveEmployeeHolidayData(employeeHolidayDto).block(), 1); } }
实体类
@Entity @Table(name = "empl_holiday_tbl") @AllArgsConstructor @NoArgsConstructor @Getter @Setter @EqualsAndHashCode public class EmployeeHolidayEntity implements Serializable{ private static final long serialVersionUID = 2429371741034836686L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id", columnDefinition = "INT UNSIGNED AUTO_INCREMENT") private Integer employeeHolidayId; @Column(name = "startDate", columnDefinition = "DATE NOT NULL") private LocalDate leaveStartDate; @Column(name = "endDate", columnDefinition = "DATE NOT NULL") private LocalDate leaveEndDate; @ManyToOne @JoinColumn(name = "empId",nullable = false,columnDefinition = "INT UNSIGNED") private EmployeeEntity employeeEntity; }
问题原因
Mockito默认使用对象equals匹配参数。测试代码中when语句传入的LocalDate.now(),与服务层调用Mapper时生成的LocalDate.now()是两个不同的实例(即使时间差极小,LocalDate的equals会精确匹配日期时间),导致参数匹配失败,Mockito返回默认的Null值。
解决方案
修改测试中的参数匹配逻辑,使用Mockito的参数匹配器来兼容动态生成的参数:
- 使用
any(LocalDate.class)匹配任意LocalDate实例 - 对于固定的
DateTimeFormatter.BASIC_ISO_DATE,可以用eq(DateTimeFormatter.BASIC_ISO_DATE)精确匹配,或者用any(DateTimeFormatter.class)
修改后的when语句如下:
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; // ... when(employeeHolidayMapper.employeeHoliDayDtoToEmployeeHolidayEntity( eq(employeeHolidayDto), any(LocalDate.class), eq(DateTimeFormatter.BASIC_ISO_DATE) )).thenReturn(employeeHolidayEntity);
这样无论服务层传入的LocalDate.now()是哪个实例,都能匹配到预设的返回值,解决Null问题。
内容的提问来源于stack exchange,提问作者Amby
相关产品推荐
相关产品推荐

