Spring Boot Mockito参数精确匹配失败及测试完善咨询
Spring Boot控制器单元测试问题分析与解决方案
问题背景
我有一个简单的Spring Boot应用,控制器代码如下:
@RestController @RequestMapping("/person") public class PersonController { @Autowired PersonService personService; @PostMapping ResponseEntity<Void> createNewMessage(@RequestBody Person itemDto, UriComponentsBuilder uriComponentsBuilder){ final Person itemInfo = personService.createPerson(itemDto) .orElseThrow(() -> new DuplicateException("ALREADY_EXISTS")); //NOTE: assume there will be a get end point UriComponents uriComponents = uriComponentsBuilder.path("/person/find/{id}").buildAndExpand(itemInfo.getId()); return ResponseEntity.created(uriComponents.toUri()).build(); } }
测试目标:使用MockMvc + Mockito,通过when安排逻辑,对该端点的DuplicateException进行单元测试。
当前测试用例执行失败(注:使用any(Person.class)时测试可通过),代码如下:
@Test void given_exact_person_save() throws Exception { var inPerson = new Person("First","Last"); when(personService.createPerson(ArgumentMatchers.eq(inPerson))).thenReturn(Optional.of(inPerson)); mockMvc.perform(post("/person") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inPerson))) .andExpect(status().isCreated()); }
服务层简易实现:
@Service public class PersonService { public Optional<Person> createPerson(Person itemDto) { return Optional.empty(); } }
疑问:问题出在哪?该如何完善我的测试?是否需要编写如下两个when语句?
when(personService.createPerson(ArgumentMatchers.eq(inPerson))).thenReturn(...); when(personService.createPerson( any(Person.class)) ).thenReturn(...);
问题根源
测试失败的核心原因是**Person类未正确重写equals()和hashCode()方法**:
- 测试中创建的
inPerson实例,经过Jackson序列化再反序列化后,控制器接收到的是一个全新的Person对象实例。 - Mockito的
eq()匹配器依赖对象的equals()方法判断是否相等,默认的equals()是基于对象引用的,两个不同实例自然匹配失败。 - 而
any(Person.class)不做实例内容校验,只要参数是Person类型就匹配,所以测试能通过。
解决方案
不需要写两个when语句,只需要解决匹配问题即可,有两种常用方案:
方案1:给Person类重写equals()和hashCode()
基于Person类的业务字段(比如firstName和lastName)实现equals()和hashCode(),可以手动编写,也可以用Lombok的@Data注解自动生成:
@Data public class Person { private String firstName; private String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
重写后,eq(inPerson)就能基于字段内容匹配,测试即可正常执行。
方案2:使用Mockito的refEq()匹配器
如果不想修改Person类,可以用refEq()匹配器,它会通过反射对比对象的字段内容:
when(personService.createPerson(ArgumentMatchers.refEq(inPerson))).thenReturn(Optional.of(inPerson));
完善DuplicateException的测试
针对DuplicateException的测试,需要模拟createPerson返回Optional.empty()的场景,验证控制器是否抛出异常并返回正确的响应:
@Test void given_duplicate_person_should_return_conflict() throws Exception { var inPerson = new Person("First", "Last"); // 模拟服务层返回空,触发异常 when(personService.createPerson(ArgumentMatchers.eq(inPerson))).thenReturn(Optional.empty()); mockMvc.perform(post("/person") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inPerson))) .andExpect(status().isConflict()) // 假设全局异常处理器将DuplicateException映射为409状态码 .andExpect(content().string("ALREADY_EXISTS")); // 验证错误信息 }
内容的提问来源于stack exchange,提问作者Kris Swat
相关产品推荐
相关产品推荐

