Mockito实战避坑指南:嵌套依赖与when/doReturn的正确姿势

Mockito实战避坑指南:嵌套依赖与when/doReturn的正确姿势

在近期针对SpringBoot组件的单元测试中,我们遭遇了两个典型的Mockito使用陷阱。本文将复盘核心问题及解决方案,帮助大家绕开同类坑点。(耗时4小时的教训总结)


一、嵌套Mock的生存法则

当测试对象存在多层级依赖注入时(如A依赖B,B依赖C),直接使用@Mock进行链式mock会导致空指针异常。通过Stack Overflow高赞方案验证,推荐采用组合拳:


// 二级依赖对象
@Spy
private MockObject1 mockObject1 = new MockObject1();

// 一级依赖对象(自动注入二级依赖)
@InjectMocks
private MockObject2 mockObject2 = spy(MockObject2.class);

// 被测对象(自动注入一级依赖)
@InjectMocks
private SystemUnderTest systemUnderTest;

实现原理

@InjectMocks会递归解析被修饰对象的成员变量,配合spy()实现对部分方法的真实调用。这种方案比纯Mock方案更贴近真实调用链路。


二、when()与doReturn()的致命差异

Mockito的这两个API存在关键行为差异

场景推荐写法注意事项
普通@Mock对象when().thenReturn()等价于doReturn写法
@Spy监控的真实对象doReturn().when()避免执行真实方法,特别适用于void方法或异常场景

错误案例警示

// 对spy对象使用when()会导致真实方法被调用!
// 引发真实调用
when(spyService.criticalOperation()).thenReturn(fakeData);
// 正确写法应阻断实际调用
doReturn(fakeData).when(spyService).criticalOperation();


经验总结

  1. 谨慎使用嵌套Mock,必要时通过@InjectMocks+spy()实现部分模拟

  2. 区分测试替身类型,@Spy对象必须使用doReturn规避副作用

  3. 复杂场景建议配合Mockito的verify()进行行为验证

本文解决方案参考:
Multiple levels of @Mock and @InjectMocks
mockito-difference-between-doreturn-and-when

### 测试 Android Fragment 和 Activity 所需的依赖 在 Android 单元测试中,测试 `Activity` 和 `Fragment` 通常需要依赖于 **Robolectric**,它可以在 JVM 上模拟 Android 环境,从而依赖真实设备或模拟器。这种能力使得测试组件生命周期、UI 控件状态以及组件间交互成为可能。 #### 本地单元测试依赖(JVM 环境) 为了测试 `Activity` 和 `Fragment`,推荐使用以下依赖配置: ```groovy dependencies { testImplementation 'junit:junit:4.13.2' // 使用 Mockito 5.x 支持 mockStatic 和 new 操作 testImplementation 'org.mockito:mockito-core:5.4.0' testImplementation 'org.mockito:mockito-inline:5.4.0' // Robolectric 用于模拟 Android 环境,支持 Activity 和 Fragment 测试 testImplementation 'org.robolectric:robolectric:4.10.3' testImplementation 'org.robolectric:androidx-support-v4:4.10.3' testImplementation 'org.robolectric:androidx-core:4.10.3' } ``` 其中,`robolectric` 提供了对 Android 组件的完整模拟,包括 `Activity` 生命周期的执行、`Fragment` 的添加交互等。此外,`androidx-support-v4` 和 `androidx-core` 的 Robolectric 模块用于支持使用 AndroidX 的项目。 #### 测试示例 测试 `Activity` 的生命周期和 UI 控件状态可以如下编写: ```java import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import static org.junit.Assert.assertNotNull; @RunWith(RobolectricTestRunner.class) @Config(sdk = [Build.VERSION_CODES.Q]) public class MyActivityTest { @Test public void testActivityLaunch() { MyActivity activity = Robolectric.buildActivity(MyActivity.class).create().start().resume().get(); assertNotNull(activity); } } ``` 测试 `Fragment` 的添加生命周期可以如下: ```java import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import static org.junit.Assert.assertNotNull; @RunWith(RobolectricTestRunner.class) @Config(sdk = [Build.VERSION_CODES.Q]) public class MyFragmentTest { @Test public void testFragmentTransaction() { MyActivity activity = Robolectric.buildActivity(MyActivity.class).create().start().resume().get(); FragmentManager fragmentManager = activity.getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); MyFragment fragment = new MyFragment(); fragmentTransaction.add(fragment, null); fragmentTransaction.commitNow(); Fragment foundFragment = fragmentManager.findFragmentById(fragment.getId()); assertNotNull(foundFragment); } } ``` 上述测试代码展示了如何使用 Robolectric 构建并操作 `Activity` 和 `Fragment`,确保其生命周期正确执行,并验证组件状态[^2]。 --- ### 优势适用场景 - **Robolectric** 允许在不依赖 Android 设备或模拟器的情况下测试 UI 组件,适用于 CI 环境和快速迭代开发。 - **JUnit** 提供了标准的测试框架结构,便于组织和执行测试用例。 - **Mockito** 可用于模拟依赖对象,结合 `mockito-inline` 可以模拟静态方法、构造函数等复杂场景。 - 适用于测试组件生命周期、UI 交互逻辑以及依赖注入行为。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值