使用Mockito编写的Service层getById单元测试是否合理?求优化建议
分析你的Mockito单元测试:getById方法测试
首先,你的现有测试存在几个明显的问题,会导致测试失败或者逻辑无效:
现有测试的核心问题
- Mock方法调用返回默认值导致断言失败:你调用了
customerService.create(customer),但customerService是@Mock出来的对象,Mock对象的方法默认会返回对应类型的默认值(Long类型就是null),所以customerId会是null,后面的assertNotNull(customerId)肯定会失败。 - 测试目标偏离:你要测试的是
get(也就是getById)方法,但这里却引入了create方法的调用,而create方法本身是Mock的,没有实际的业务逻辑,这部分完全是多余的,反而干扰了测试的核心逻辑。
改进后的标准测试代码
针对getById方法的单元测试,核心应该是:Mock出方法的输入输出,验证调用行为和返回结果是否符合预期。这里是修正后的代码:
@Mock private CustomerService customerService; @Test public void whenGetCustomerById_thenReturnExpectedCustomer() { // 1. 准备测试数据 Long targetCustomerId = 1L; Customer expectedCustomer = Customer.builder() .firstName("Name") .lastName("Last Name") .email("name.last@mail.com") .build(); // 2. 定义Mock行为:当传入指定ID调用get方法时,返回预期的Customer对象 when(customerService.get(targetCustomerId)).thenReturn(expectedCustomer); // 3. 执行要测试的方法 Customer actualCustomer = customerService.get(targetCustomerId); // 4. 验证返回结果的正确性 assertNotNull(actualCustomer); assertEquals(expectedCustomer.getFirstName(), actualCustomer.getFirstName()); assertEquals(expectedCustomer.getLastName(), actualCustomer.getLastName()); assertEquals(expectedCustomer.getEmail(), actualCustomer.getEmail()); // 建议加上邮箱的断言,覆盖更多字段 // 5. 验证Mock方法的调用次数是否符合预期 verify(customerService, times(1)).get(targetCustomerId); }
更简洁的BDD风格写法
Mockito支持BDD(行为驱动开发)风格的语法,让测试代码的可读性更强,结构更清晰:
@Mock private CustomerService customerService; @Test public void getCustomerById_ShouldReturnExpectedCustomer() { // Given:准备测试场景和数据 Long targetCustomerId = 1L; Customer expectedCustomer = Customer.builder() .firstName("Name") .lastName("Last Name") .email("name.last@mail.com") .build(); // When:执行测试动作 given(customerService.get(targetCustomerId)).willReturn(expectedCustomer); Customer actualCustomer = customerService.get(targetCustomerId); // Then:验证结果和行为 then(actualCustomer).isEqualTo(expectedCustomer); // 如果你的项目引入了AssertJ,可以用更简洁的断言 then(customerService).should(times(1)).get(targetCustomerId); }
关于测试@Service中getById方法的最佳实践
这是完全合理且推荐的实践,但要根据方法的实际逻辑来判断测试的价值:
- 如果你的
CustomerService的getById方法包含业务逻辑(比如权限校验、数据转换、关联数据的处理等),那么用Mockito隔离底层依赖(比如DAO层的Repository),专注测试Service层的业务逻辑,是非常合适的单元测试场景。 - 如果
getById只是简单的转发调用Repository的findById方法,没有任何额外的业务逻辑,那么这类单元测试的价值较低,更适合在集成测试中覆盖(比如用@DataJpaTest测试Repository的正确性,或者用@SpringBootTest测试完整的服务调用流程)。 - 单元测试的核心是隔离依赖,测试单一职责,只要你的Service方法有独立的业务逻辑需要验证,用Mockito+JUnit测试就是良好的实践。
内容的提问来源于stack exchange,提问作者Luzito Hop
相关产品推荐
相关产品推荐

