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

Java应用Service层测试报错求助:Mock静态Mapper失败及级联测试疑问

Java Service层测试问题解决指南

一、静态Mapper方法Mock报错分析与解决

报错原因(翻译后报错信息)

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() 需要一个参数,该参数必须是“对mock对象的方法调用”。
例如:
when(mock.getArticles()).thenReturn(articles);

此外,该错误可能出现的原因:
1. 你尝试Stub以下类型的方法:final/private/equals()/hashCode()方法。
   这些方法**无法**被Stub/验证。不支持Mock非公开父类中声明的方法。
2. 在when()中你调用的不是mock对象的方法,而是其他对象的方法。

核心问题:PatientMapper.mapToEntity是静态方法,Mockito默认仅支持Mock实例方法,静态方法不属于任何mock对象,因此直接用when()会触发上述异常。

解决方案

方案1:使用Mockito静态Mock(适配Mockito 3.4+版本)

通过MockedStatic类对静态类进行Mock,需放在try-with-resources块中确保测试后清理:

@ExtendWith(MockitoExtension.class)
public class PatientServiceTest {

    @Mock
    private PatientDao patientDao;

    @InjectMocks
    private PatientServiceImpl patientService;

    @Test
    public void testSave() {
        PatientEntity patient = new PatientEntity();
        patient.setId(1L);
        PatientTO patientTO = new PatientTO();
        patientTO.setId(1L);

        // Mock静态Mapper类
        try (MockedStatic<PatientMapper> mockedMapper = Mockito.mockStatic(PatientMapper.class)) {
            mockedMapper.when(() -> PatientMapper.mapToEntity(patientTO)).thenReturn(patient);
            mockedMapper.when(() -> PatientMapper.mapToTO(patient)).thenReturn(patientTO);

            when(patientDao.save(patient)).thenReturn(patient);

            PatientTO result = patientService.save(patientTO);

            assertNotNull(result);
            verify(patientDao, times(1)).save(patient);
            // 验证静态方法调用次数
            mockedMapper.verify(() -> PatientMapper.mapToEntity(patientTO), times(1));
            mockedMapper.verify(() -> PatientMapper.mapToTO(patient), times(1));
        }
    }
}

方案2:重构Mapper为非静态类(推荐)

将静态Mapper改为实例类并通过依赖注入使用,更符合Spring设计原则,测试更简洁:

  1. 修改Mapper类
@Component
public class PatientMapper {
    public PatientTO mapToTO(final PatientEntity patientEntity) {
        // 原逻辑不变,移除static修饰符
        if (patientEntity == null) return null;

        final PatientTO patientTO = new PatientTO();
        patientTO.setId(patientEntity.getId());
        patientTO.setFirstName(patientEntity.getFirstName());
        patientTO.setLastName(patientEntity.getLastName());
        patientTO.setTelephoneNumber(patientEntity.getTelephoneNumber());
        patientTO.setEmail(patientEntity.getEmail());
        patientTO.setPatientNumber(patientEntity.getPatientNumber());
        patientTO.setDateOfBirth(patientEntity.getDateOfBirth());
        patientTO.setDoesHaveChronicDiseases(patientEntity.getDoesHaveChronicDisease());

        return patientTO;
    }

    public PatientEntity mapToEntity(final PatientTO patientTO) {
        // 原逻辑不变,移除static修饰符
        if (patientTO == null) return null;

        final PatientEntity patientEntity = new PatientEntity();
        patientEntity.setId(patientTO.getId());
        patientEntity.setFirstName(patientTO.getFirstName());
        patientEntity.setLastName(patientTO.getLastName());
        patientEntity.setTelephoneNumber(patientTO.getTelephoneNumber());
        patientEntity.setEmail(patientTO.getEmail());
        patientEntity.setPatientNumber(patientTO.getPatientNumber());
        patientEntity.setDateOfBirth(patientTO.getDateOfBirth());
        patientEntity.setDoesHaveChronicDisease(patientTO.getDoesHaveChronicDiseases());

        return patientEntity;
    }
}
  1. 修改Service类注入Mapper
@Service
@Transactional
public class PatientServiceImpl implements PatientService {

    private final PatientDao patientDao;
    private final PatientMapper patientMapper;

    @Autowired
    public PatientServiceImpl(PatientDao patientDao, PatientMapper patientMapper) {
        this.patientDao = patientDao;
        this.patientMapper = patientMapper;
    }

    @Override
    public PatientTO findById(Long ID) {
        final PatientEntity patientEntity = patientDao.findOne(ID);
        return patientMapper.mapToTO(patientEntity);
    }

    @Override
    public PatientTO save(PatientTO patientTO) {
        final PatientEntity patientEntity = patientMapper.mapToEntity(patientTO);
        patientDao.save(patientEntity);
        return patientMapper.mapToTO(patientEntity);
    }
}
  1. 测试类Mock实例Mapper
@ExtendWith(MockitoExtension.class)
public class PatientServiceTest {

    @Mock
    private PatientDao patientDao;

    @Mock
    private PatientMapper patientMapper;

    @InjectMocks
    private PatientServiceImpl patientService;

    @Test
    public void testSave() {
        PatientEntity patient = new PatientEntity();
        patient.setId(1L);
        PatientTO patientTO = new PatientTO();
        patientTO.setId(1L);

        when(patientMapper.mapToEntity(patientTO)).thenReturn(patient);
        when(patientMapper.mapToTO(patient)).thenReturn(patientTO);
        when(patientDao.save(patient)).thenReturn(patient);

        PatientTO result = patientService.save(patientTO);

        assertNotNull(result);
        verify(patientDao, times(1)).save(patient);
        verify(patientMapper, times(1)).mapToEntity(patientTO);
        verify(patientMapper, times(1)).mapToTO(patient);
    }
}

二、验证删除患者时级联删除预约的测试方法

情况1:JPA级联删除(ORM层面自动处理)

如果PatientEntity与AppointmentEntity配置了@OneToMany(cascade = CascadeType.REMOVE),Service只需调用patientDao.delete(),级联由JPA自动完成。

  • 单元测试:验证Service调用了删除患者的方法
@Test
public void testDeletePatientCascadesAppointments() {
    Long patientId = 1L;
    PatientEntity patient = new PatientEntity();
    patient.setId(patientId);

    when(patientDao.findOne(patientId)).thenReturn(patient);

    patientService.delete(patientId);

    verify(patientDao, times(1)).delete(patient);
}
  • 集成测试:验证实际级联效果(用内存数据库如H2)
@SpringBootTest
@Transactional
public class PatientServiceIntegrationTest {

    @Autowired
    private PatientService patientService;

    @Autowired
    private PatientDao patientDao;

    @Autowired
    private AppointmentDao appointmentDao;

    @Test
    public void testDeletePatientCascadesAppointments() {
        // 准备测试数据
        PatientEntity patient = new PatientEntity();
        patient.setFirstName("Test");
        patientDao.save(patient);

        AppointmentEntity appointment = new AppointmentEntity();
        appointment.setPatient(patient);
        appointmentDao.save(appointment);

        // 删除患者
        patientService.delete(patient.getId());

        // 验证数据被级联删除
        assertNull(patientDao.findOne(patient.getId()));
        assertNull(appointmentDao.findOne(appointment.getId()));
    }
}

情况2:Service层手动处理级联删除

如果Service先删除关联预约再删除患者,测试时需验证两个操作都被执行:

@ExtendWith(MockitoExtension.class)
public class PatientServiceTest {

    @Mock
    private PatientDao patientDao;

    @Mock
    private AppointmentDao appointmentDao;

    @InjectMocks
    private PatientServiceImpl patientService;

    @Test
    public void testDeletePatientCascadesAppointments() {
        Long patientId = 1L;

        patientService.delete(patientId);

        // 验证先删除关联预约
        verify(appointmentDao, times(1)).deleteByPatientId(patientId);
        // 验证再删除患者
        verify(patientDao, times(1)).deleteById(patientId);
    }
}

内容的提问来源于stack exchange,提问作者SlipperyBarrel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:43:09