You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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的参数匹配器来兼容动态生成的参数:

  1. 使用any(LocalDate.class)匹配任意LocalDate实例
  2. 对于固定的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:33:09