service层单元测试需只测逻辑、不碰依赖,用mockito模拟repository、其他service等外部组件,覆盖正常、边界、异常三类场景,并避免静态变量干扰。

Service 层单元测试的关键是“只测逻辑,不碰依赖”。你要让被测 Service 跑在干净、可控的环境里,所有外部协作对象(比如 DAO、其他 Service、第三方客户端)都用 Mock 替代,确保测试结果只反映它自己代码的对错。
明确被测对象与依赖边界
先理清 Service 类真正调用哪些外部组件。常见依赖包括:
- Repository 或 Mapper(访问数据库)
- 其他 Service(如 OrderService 调用 UserService)
- 工具类或 SDK(如短信发送器、加密服务)
- 配置类或静态方法(需特别注意,易污染测试)
这些都不能真实执行——否则就不是单元测试,而是集成测试了。目标是:输入确定数据 → 执行 Service 方法 → 验证返回值或内部行为(如是否调用了某个依赖的方法)。
用 Mockito 创建和注入 Mock 对象
推荐使用 @ExtendWith(MockitoExtension.class) + @Mock + @InjectMocks 组合,这是 JUnit 5 下最简洁的写法:
-
@Mock:为每个依赖创建一个模拟对象,比如
@Mock private UserMapper userMapper; - @InjectMocks:自动把所有 @Mock 对象通过构造器或 setter 注入到被测 Service 实例中
- 无需手动 new Service,也无需 Spring 容器参与
例如:UserService userService = new UserService(userMapper); 这种手动构造方式也完全可行,尤其适合构造器注入明确的场景,更利于看清依赖关系。
针对不同依赖行为编写测试用例
不能只测“正常流程”。要覆盖三种典型交互路径:
- 当依赖返回 true / 正常数据 时,Service 是否走通主逻辑(如保存、计算、返回结果)
- 当依赖返回 false / null / 空集合 时,Service 是否正确处理边界(如跳过后续操作、返回默认值)
- 当依赖抛出 异常(如 RuntimeException、自定义业务异常)时,Service 是否捕获、转换、重试或透传,避免崩溃或状态不一致
每种情况都对应一个独立的 @Test 方法,用 when(...).thenReturn(...) 或 when(...).thenThrow(...) 控制依赖行为,再用 verify(...).method() 检查是否按预期调用了依赖。
避开静态变量和全局状态陷阱
如果 Service 内部用了可变静态字段(如 public static int retryCount = 0;),多个测试会互相干扰。解决方式很直接:
- 测试类里禁用任何 static 可变字段,改用实例变量 + @BeforeEach 初始化
- 把静态工具调用(如
DateUtils.now())抽成接口,测试时注入返回固定时间的实现 - 必须用上下文信息(如用户ID、租户)时,优先用 ThreadLocal,它天然支持单测线程隔离
原则是:让测试不依赖任何“看不见的共享状态”,才能稳定、可重复。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











