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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 12:57:07