使用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
错误原因
- Stub逻辑冲突:你同时尝试mock
jwtServices.getDecoderJwt()的返回值,又保留了jwtDecoder和payload的mock对象。由于jwtServices是被@InjectMocks注入的真实对象,执行时会调用其真实逻辑,此时payload.getSub()未被指定返回值,Mockito内部错误地将你设置的jwtData(HashMap)关联到该方法,导致类型不匹配。 - 测试逻辑错误:直接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
相关产品推荐
相关产品推荐

