Spring Boot中Mockito对同一Service类部分方法Mock失效问题
问题排查:Spring Boot Controller测试中Service的postPerson Mock失效
我在Java Spring Boot应用中测试Controller类,该Controller依赖一个Service类。测试时创建了listPerson集合,对Service类Mock后,when(.getAPerson).thenReturn(listPersons)可以正常生效,能获取到测试中创建的人员列表;但同一Service类的postPerson方法Mock失效,调用后返回空列表,导致测试testAddPerson时预期返回201实际返回404。
Controller代码
// get a person @GetMapping(value = "person/{firstNamelastName}") public ResponseEntity<List<Persons>> getAPerson(@PathVariable String firstNamelastName, HttpServletRequest request, HttpServletResponse response) { String elemjson = "persons"; List<Persons> listP = new ArrayList<>(); try { listP = repositorySafetyNetInterface.getAPerson(firstNamelastName, elemjson); if (listP.isEmpty()) { response.setStatus(204); messagelogger = "No Content. The person does not exist." + loggerApiNewSafetyNet.loggerInfo(request, response, firstNamelastName); LOGGER.info(messagelogger); return ResponseEntity.status(response.getStatus()).build(); } } catch (IOException e) { response.setStatus(404); return ResponseEntity.status(response.getStatus()).build(); } catch (NullPointerException nulle) { response.setStatus(404); return null; } messagelogger = "OK" + loggerApiNewSafetyNet.loggerInfo(request, response, firstNamelastName); LOGGER.info(messagelogger); return new ResponseEntity<>(listP, HttpStatus.valueOf(response.getStatus())); } // add a person @PostMapping(value = "/person") public ResponseEntity addPerson(@RequestBody Persons person, HttpServletRequest request, HttpServletResponse response) { List<Persons> listP = new ArrayList<>(); try { listP = repositoryElementJson.getPersons("persons"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } listP = repositorySafetyNetInterface.postPerson(person); if (listP.isEmpty()) { response.setStatus(404); messagelogger = "No persons added. " + RESPONSSTATUS + response.getStatus() + ":" + loggerApiNewSafetyNet.loggerInfo(request, response, ""); LOGGER.info(messagelogger); return ResponseEntity.status(response.getStatus()).build(); } response.setStatus(201); URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{firstName+lastName}") .buildAndExpand(person.getFirstName() + person.getLastName()).toUri(); messagelogger = "A new person is added successful. The URL is : " + location; LOGGER.info(messagelogger); messagelogger = "Created " + loggerApiNewSafetyNet.loggerInfo(request, response, ""); LOGGER.info(messagelogger); return new ResponseEntity<>(person, HttpStatus.valueOf(response.getStatus())); }
Service类代码
@Override public List<Persons> getAPerson(String firstNamelastName, String elemjson) throws IOException { List<Persons> listPersons = new ArrayList<>(); List<Persons> listP = listPersons; listPersons = getListsElementsJsonRepository.getPersons(elemjson); for (Persons element : listPersons) { if ((element.getFirstName().trim() + element.getLastName().trim()).equalsIgnoreCase(firstNamelastName)) { listP.add(element); } } LOGGER.debug("Get the person is ok."); return listP; } @Override public List<Persons> postPerson(Persons person) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(loggerApiNewSafetyNet.loggerDebug(person.toString())); } List<Persons> listPersons; listPersons = createListPersons(); if (LOGGER.isDebugEnabled()) { messageLogger = "The persons are: " + listPersons; LOGGER.debug(messageLogger); } boolean findperson = false; for (Persons element : listPersons) { if ((element.getFirstName() + element.getLastName()) .equals(person.getFirstName() + person.getLastName())) { findperson = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("This person is already in the list."); } break; } } if (!findperson) { Boolean filecreated = false; listPersons.add(person); if (LOGGER.isDebugEnabled()) { messageLogger = "The person is added in the list: " + listPersons; LOGGER.debug(messageLogger); } List<Firestations> listFirestations; listFirestations = createListFirestations(); List<Medicalrecords> listMedicalrecords; listMedicalrecords = createListMedicalrecords(); newFileJson.createNewFileJson(listPersons, listFirestations, listMedicalrecords, person.toString()); filecreated = newFileJson.isFileCreated(); if (Boolean.TRUE.equals(filecreated)) { List<Persons> listP; listP = verifyIfTheNewPersonInTheFile(person); return listP; } } return Collections.emptyList(); }
测试类代码
@WebMvcTest(controllers = NewSafetyAlertController.class) public class NewSafetyAlertControllerTests { private static final Logger LOGGER = LogManager.getLogger(NewSafetyAlertControllerTests.class); @Autowired private MockMvc mockMvc; @MockBean private SafetyNetInterface safetyNetServiceInterface; @Test void testAddPerson() throws Exception { String firstName = "TEST999_FirstName"; String lastName = "TEST999_LastName"; String elemjson = "persons"; List<Persons> listPersons; Persons person; person = createPerson(firstName, lastName); listPersons = createListPersonsTest(firstName, lastName, person); String body = "{\r\n" + "\"firstName\": \"" + firstName + "\",\r\n" + "\"lastName\": \"" + lastName + "\",\r\n" + "\"address\": \"1509 Culver St\",\r\n" + "\"city\": \"Culver\",\r\n" + "\"zip\": \"97451\",\r\n" + "\"phone\": \"841-874-6512\",\r\n" + "\"email\": \"jaboyd@email.com\"\r\n" + "}"; when(safetyNetServiceInterface.postPerson(person)).thenReturn(listPersons); when(safetyNetServiceInterface.getAPerson(firstName + lastName, elemjson)).thenReturn(listPersons); mockMvc.perform(post("/person").content(body).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is(201)); }
排查原因
1. 对象匹配问题
Mockito的when()默认使用对象相等性匹配。你在测试中手动创建了person对象,但Controller中通过@RequestBody反序列化得到的是另一个新的Persons实例——这两个对象属性值相同,但不是同一个对象,导致Mock的postPerson(person)不生效,Mockito返回默认的空列表。
而getAPerson方法的参数是字符串,字符串的相等性基于内容判定,所以匹配成功,Mock正常生效。
2. 解决方案
- 使用参数匹配器:将Mock中的参数替换为
any(Persons.class),只要是Persons类型的参数都能匹配:when(safetyNetServiceInterface.postPerson(any(Persons.class))).thenReturn(listPersons); - 重写Persons类的equals和hashCode方法:让属性相同的
Persons对象被判定为相等,这样Mockito就能匹配到你手动创建的person对象。 - 使用refEq匹配器:如果不想重写equals,可以用
Mockito.refEq(person)匹配属性相等的对象:when(safetyNetServiceInterface.postPerson(refEq(person))).thenReturn(listPersons);
3. 额外检查点
- 确认Controller中调用的
repositorySafetyNetInterface就是你Mock的safetyNetServiceInterface,没有注入错误实例。 - 可以在测试中添加日志或断点,查看Controller接收到的
person对象属性是否和手动创建的一致,避免反序列化时出现属性不匹配的情况。
内容的提问来源于stack exchange,提问作者Claudiu
相关产品推荐
相关产品推荐

