Android Instrumentation测试中如何在Intent中传递Mock类对象?
你的问题本质上是两个核心问题:Mockito生成的代理类无法被Android的序列化机制正确处理,以及launchActivity时Intent会被系统复制导致Spy的Intent失效,下面我来逐一解释并给出可行的解决办法:
为什么原来的代码会失败?
1. Mock对象序列化报错的原因
即使你给@Mock加上了serializable = "true",并且SomeClass实现了Serializable接口,Mockito动态生成的Mock代理类内部包含了大量Mockito的私有状态(比如存根逻辑、验证信息等),这些内部成员并没有实现Serializable。当Android尝试把这个Mock对象写入Parcel(Serializable在Android中是通过Parcel机制处理的)时,就会抛出Parcelable encountered IOException writing serializable object异常。
2. Spy的Intent在Activity中失效的原因
当你调用mMainActivityTestRule.launchActivity(intent)时,Android系统并不会直接传递你创建的Spy Intent实例——它会复制这个Intent的内容,生成一个新的Intent对象传递给Activity。所以你在测试中给Spy Intent设置的when(intent.getSerializableExtra(...)).thenReturn(...)存根完全无效,因为Activity中调用该方法的是一个全新的Intent实例,自然返回null。
可行的解决方案
方案1:绕过Intent传递,直接给Activity注入Mock对象
这是最推荐的方案,既避免了序列化问题,也让测试逻辑更清晰。你可以在Activity中添加一个测试专用的方法,用于注入Mock对象:
// MainActivity.java public class MainActivity extends AppCompatActivity { private SomeClass someClass; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 正常流程从Intent获取,测试时优先使用注入的对象 if (someClass == null) { someClass = (SomeClass) getIntent().getSerializableExtra("someClass"); } // 后续业务逻辑... } // 测试专用方法:设置Mock对象 public void setSomeClassForTesting(SomeClass someClass) { this.someClass = someClass; } }
然后在测试中,先启动Activity,再注入Mock对象:
@RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Mock SomeClass someClass; @Rule public ActivityTestRule<MainActivity> mMainActivityTestRule = new ActivityTestRule<>(MainActivity.class, false, false); @Rule public MockitoRule rule = MockitoJUnit.rule(); @Test public void testGetQuote() throws Exception { // 配置Mock行为 when(someClass.getFoo(anyString())).thenReturn(expectedAnswer); // 启动Activity(不需要传递Intent) MainActivity activity = mMainActivityTestRule.launchActivity(null); // 注入Mock对象 activity.setSomeClassForTesting(someClass); // 执行测试逻辑,比如验证业务流程... } }
方案2:使用真实对象替代Mock/Spy(如果业务允许)
如果SomeClass的实例可以轻松创建,并且只需要修改部分状态或方法行为,可以创建真实对象,并用Mockito.spy()包装它(注意:如果要序列化这个Spy对象,仍然可能遇到问题,所以更适合不需要传递Intent的场景):
@Test public void testGetQuote() throws Exception { // 创建真实对象并Spy SomeClass realSomeClass = new SomeClass(); SomeClass spiedSomeClass = spy(realSomeClass); // 修改Spy对象的方法行为 when(spiedSomeClass.getFoo(anyString())).thenReturn(expectedAnswer); // 如果必须传递Intent,这里用真实对象的话可以正常序列化 Intent intent = new Intent(); intent.putExtra("someClass", realSomeClass); mMainActivityTestRule.launchActivity(intent); // 后续测试逻辑... }
方案3:避免通过Intent传递复杂对象(最佳实践)
从架构设计角度,尽量不要通过Intent传递Serializable/Parcelable的复杂对象,而是改用依赖注入框架(比如Dagger Hilt)来管理对象实例。这样在测试时,你可以轻松替换成Mock对象,完全不需要依赖Intent传递,代码也更解耦。
内容的提问来源于stack exchange,提问作者user2465039

