
本文详解 Mockito 中 @Mock + @InjectMocks 失效的根本原因——被测类未通过构造器或 setter 接收依赖,导致 new 实例绕过 Mock 对象;提供可落地的重构方案与完整测试示例。
本文详解 mockito 中 `@mock` + `@injectmocks` 失效的根本原因——被测类未通过构造器或 setter 接收依赖,导致 new 实例绕过 mock 对象;提供可落地的重构方案与完整测试示例。
在使用 Mockito 编写单元测试时,一个常见却容易被忽视的问题是:明明已声明 @Mock 并配置了 when(...).thenReturn(...),但运行测试时仍触发了被模拟类的真实方法逻辑(如打印日志、发起网络请求、执行加密等)。你遇到的正是这一典型场景。
根本原因在于:ServiceClass 在 sendPayload() 方法内部直接通过 new EncSercice() 创建了新实例,完全绕过了由 @InjectMocks 注入的 Mock 对象。Mockito 只能控制它所管理的依赖对象(即 encService 字段),无法干预你在方法体内手动 new 出来的任何实例。
✅ 正确做法:将依赖作为构造参数注入(推荐)
修改 ServiceClass,使其显式接收 EncSercice 实例,而非自行创建:
public class ServiceClass {
private final EncSercice encService; // 建议 final + 构造注入,增强可测性与不可变性
// 构造器注入 —— Mockito @InjectMocks 会自动匹配并注入 @Mock 标记的字段
public ServiceClass(EncSercice encService) {
this.encService = Objects.requireNonNull(encService, "encService must not be null");
}
public void sendPayload(final ValidRequest req) {
// ... 其他业务逻辑
String[] keys = {/* your keys */};
try {
String str = encService.callFn(req.toString(), keys); // ← 现在调用的是注入的 mock 实例!
// 处理返回结果...
} catch (Exception e) {
throw new RuntimeException("Encryption failed", e);
}
}
}
✅ 对应测试代码(修正后)
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
class ServiceClassTest {
@Mock
private EncSercice encService;
private ServiceClass service;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this); // JUnit 5 推荐用法(替代已废弃的 initMocks)
service = new ServiceClass(encService); // 手动构造,确保 mock 被传入
}
@Test
void testEncryptRegisterTransactionPayload() {
// Given
ValidRequest request = new ValidRequest();
String stubbedResult = "Stubbed encrypted message";
when(encService.callFn(anyString(), any(String[].class)))
.thenReturn(stubbedResult);
// When
service.sendPayload(request);
// Then
verify(encService).callFn(eq(request.toString()), any(String[].class));
// 可选:验证无异常抛出、或检查其他副作用
}
}
⚠️ 注意事项与最佳实践
- 避免 new 实例:被测类中所有外部依赖(尤其是可能需要 Mock 的服务)必须通过构造器、setter 或依赖注入框架(如 Spring)传入,禁止硬编码 new。
- @InjectMocks 不是魔法:它仅对字段注入生效(且要求字段名/类型匹配),若类无对应字段(如你原代码中 service 是局部变量或未声明为字段),@InjectMocks 将静默失败。
- JUnit 5 提示:MockitoAnnotations.initMocks(this) 已弃用,请改用 MockitoAnnotations.openMocks(this) 并在 @AfterEach 中关闭(或使用 @ExtendWith(MockitoExtension.class) 自动管理生命周期)。
- 接口优于实现:EncSercice 应定义为接口,EncSerciceImp 作为其实现类,这样 Mock 更自然、解耦更彻底。
-
动态返回值?用 thenAnswer:若需根据入参动态生成返回值(如模拟不同密钥返回不同密文),可改用:
when(encService.callFn(anyString(), any(String[].class))) .thenAnswer(invocation -> { String input = invocation.getArgument(0); String[] keys = invocation.getArgument(1); return "ENCRYPTED_" + input.hashCode(); // 自定义逻辑 });
通过以上重构,你的测试将真正实现“隔离依赖、可控行为”,Mockito 不再调用真实 callFn,而是严格按预设规则响应,大幅提升测试稳定性与执行效率。











