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

使用JUnit和Mockito测试:解决返回类型错误与Null问题

JUnit+Mockito测试PatientServicesImpl时的返回类型错误解决

问题背景

实现了PatientServicesImpl类,其findByPatient方法依赖JwtServices解析JWT数据,再调用外部接口查询患者信息。相关核心代码如下:

PatientServicesImpl核心代码

@Autowired
private JwtServices jwtServices;

@Override
public Map<String, Object> findByPatient(String identificationNumber, String birthdate, Boolean allowPatientData)  {
    Map<String, String> jwtData = jwtServices.getDecoderJwt();

    Map<String, String> dataPatient =  new HashMap<>();
    dataPatient.put("identificationNumber", identificationNumber);
    dataPatient.put("birthdate",birthdate);
    dataPatient.put("allowPatientData",allowPatientData.toString());
    dataPatient.put("appointmentId",jwtData.get("sub"));

    return this.apiFaroConfig.findPatient(dataPatient);
}

JwtServices核心代码

@Autowired
private JWTDecoder jwtDecoder;

public Map<String, String> getDecoderJwt(){
    Map<String, String> jwtData = new HashMap<>();

    Payload payloadHeaderAuthorization = jwtDecoder.getPayloadAuthorization();

    jwtData.put("iss", payloadHeaderAuthorization.getIss());
    jwtData.put("sub",  payloadHeaderAuthorization.getSub());

    return jwtData;
}

编写测试代码后运行抛出错误:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
HashMap cannot be returned by getSub()
getSub() should return String

错误原因

  1. Stub逻辑冲突:你同时尝试mockjwtServices.getDecoderJwt()的返回值,又保留了jwtDecoder和payload的mock对象。由于jwtServices是被@InjectMocks注入的真实对象,执行时会调用其真实逻辑,此时payload.getSub()未被指定返回值,Mockito内部错误地将你设置的jwtData(HashMap)关联到该方法,导致类型不匹配。
  2. 测试逻辑错误:直接stub了要测试的patientServices.findByPatient()方法,完全跳过了目标方法的真实执行逻辑;最后调用的是外部接口方法而非目标测试方法,失去了测试意义。

修正后的测试代码

方案一:直接Mock JwtServices(推荐,专注测试PatientServicesImpl逻辑)

该方案将JwtServices视为外部依赖直接mock,无需关注其内部实现,让测试聚焦于目标类的业务逻辑:

@ExtendWith(MockitoExtension.class)
class PatientServicesImplTest {

    @Mock
    private FaroApiRest apiFaroRest;

    @Mock // 直接mock JwtServices,隔离内部实现
    private JwtServices jwtServices;

    @InjectMocks
    private PatientServicesImpl patientServices;

    private Map<String, String> expectedDataPatient;
    private Map<String, Object> mockResponse;
    private Map<String, String> mockJwtData;

    @BeforeEach
    void setUp() {
        // 初始化mock JWT数据
        mockJwtData = new HashMap<>();
        mockJwtData.put("sub", "123456");

        // 构造预期传给外部接口的参数
        expectedDataPatient = new HashMap<>();
        expectedDataPatient.put("identificationNumber", "XXXX");
        expectedDataPatient.put("birthdate", "XXXX");
        expectedDataPatient.put("allowPatientData", "true");
        expectedDataPatient.put("appointmentId", "123456");

        // 初始化外部接口的mock返回值
        mockResponse = new HashMap<>();
        mockResponse.put("datosPersonales", "XXX");
        mockResponse.put("anamnesis", "XXX");
        mockResponse.put("gdpr", "XXX");
    }

    @Test
    void findByPatient_shouldReturnPatientData() {
        // 1. Stub依赖方法
        when(jwtServices.getDecoderJwt()).thenReturn(mockJwtData);
        when(apiFaroRest.findPatient(expectedDataPatient)).thenReturn(mockResponse);

        // 2. 调用要测试的方法
        Map<String, Object> actualResponse = patientServices.findByPatient("XXXX", "XXXX", true);

        // 3. 验证结果与依赖调用
        assertNotNull(actualResponse);
        assertTrue(actualResponse.containsKey("datosPersonales"));
        assertEquals(mockResponse, actualResponse);
        verify(apiFaroRest).findPatient(expectedDataPatient);
        verify(jwtServices).getDecoderJwt();
    }
}

方案二:完整Mock JwtServices的依赖(适合验证JwtServices内部逻辑场景)

如果需要验证JwtServices的内部调用链,可以保留真实的JwtServices,并完整mock其依赖:

@ExtendWith(MockitoExtension.class)
class PatientServicesImplTest {

    @Mock
    private FaroApiRest apiFaroRest;

    @InjectMocks
    private JwtServices jwtServices; // 使用真实JwtServices,mock其依赖

    @Mock
    private JWTDecoder jwtDecoder;

    @Mock
    private Payload payload;

    @InjectMocks
    private PatientServicesImpl patientServices;

    private Map<String, String> expectedDataPatient;
    private Map<String, Object> mockResponse;

    @BeforeEach
    void setUp() {
        // Stub Payload的返回值,确保类型正确
        when(payload.getIss()).thenReturn("560");
        when(payload.getSub()).thenReturn("123456");

        // Stub JWTDecoder返回mock的Payload
        when(jwtDecoder.getPayloadAuthorization()).thenReturn(payload);

        // 构造预期传给外部接口的参数
        expectedDataPatient = new HashMap<>();
        expectedDataPatient.put("identificationNumber", "XXXX");
        expectedDataPatient.put("birthdate", "XXXX");
        expectedDataPatient.put("allowPatientData", "true");
        expectedDataPatient.put("appointmentId", "123456");

        // 初始化外部接口的mock返回值
        mockResponse = new HashMap<>();
        mockResponse.put("datosPersonales", "XXX");
        mockResponse.put("anamnesis", "XXX");
        mockResponse.put("gdpr", "XXX");
    }

    @Test
    void findByPatient_shouldReturnPatientData() {
        // Stub外部接口返回值
        when(apiFaroRest.findPatient(expectedDataPatient)).thenReturn(mockResponse);

        // 调用测试方法
        Map<String, Object> actualResponse = patientServices.findByPatient("XXXX", "XXXX", true);

        // 验证结果与依赖调用链
        assertNotNull(actualResponse);
        assertEquals(mockResponse, actualResponse);
        verify(jwtDecoder).getPayloadAuthorization();
        verify(payload).getSub();
        verify(apiFaroRest).findPatient(expectedDataPatient);
    }
}

关键修正点

  • 明确测试边界:测试PatientServicesImpl时,优先将JwtServices视为外部依赖mock,避免被其内部实现干扰。
  • 移除错误Stub:不要stub目标测试方法,直接调用并验证其行为和返回值。
  • 正确Stub依赖:若使用真实依赖类,必须完整Stub其所有依赖方法的返回值,确保类型匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:24:56