Android中RecyclerView及其适配器的Mockito单元测试问题
解决RecyclerView适配器Mockito单元测试的空指针问题
Hey there! 作为Android新手刚接触Mockito和RecyclerView单元测试碰到空指针太正常了,我来帮你捋捋常见的坑和靠谱的解决思路~
首先得明确:单元测试的核心是测逻辑,不是测Android系统组件的渲染。你不用硬去mock整个RecyclerView再绑定适配器——RecyclerView内部有一堆依赖(比如LayoutManager、RecycledViewPool),mock这些很容易因为没覆盖到内部调用触发空指针。咱们换个思路,把适配器和系统组件解耦来测。
一、先搞清楚你要测什么
先把测试目标拆成小模块,比如:
- 适配器的item数量是否和数据源匹配
- item点击/长按事件是否正确触发回调
- 数据更新后适配器是否正确刷新UI
二、常见空指针的原因&解决
你之前mock Activity和RecyclerView后出现空指针,大概率是因为:
- Mock的RecyclerView没有覆盖内部方法(比如
getContext()、getLayoutManager()),适配器绑定的时候调用了这些方法导致空指针 - 适配器依赖了Activity里未mock的对象(比如Context、资源),初始化时出问题
解决办法:单独测试适配器,完全不依赖RecyclerView或Activity的真实/ mock实例
三、实战测试例子
1. 测试item点击事件
假设你的适配器是这样的(带点击回调):
class MyAdapter( private val itemList: List<String>, private val onItemClick: (String) -> Unit ) : RecyclerView.Adapter<MyAdapter.ItemViewHolder>() { inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: String) { itemView.setOnClickListener { onItemClick(item) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_layout, parent, false) return ItemViewHolder(view) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { holder.bind(itemList[position]) } override fun getItemCount() = itemList.size }
测试点击的话,不用碰RecyclerView,直接mock关键对象就行:
import org.junit.Test import org.mockito.Mockito.* class MyAdapterTest { @Test fun `item click triggers callback with correct data`() { // 1. 准备测试数据 val testItems = listOf("Apple", "Banana", "Cherry") // 2. Mock点击回调接口 val mockClickCallback = mock<(String) -> Unit>() // 3. 初始化适配器 val adapter = MyAdapter(testItems, mockClickCallback) // 4. Mock一个ViewGroup(onCreateViewHolder需要它)和View val mockParent = mock<ViewGroup>() val mockContext = mock<Context>() `when`(mockParent.context).thenReturn(mockContext) val mockItemView = mock<View>() `when`(LayoutInflater.from(mockContext).inflate(anyInt(), eq(mockParent), eq(false))) .thenReturn(mockItemView) // 5. 创建ViewHolder并绑定数据 val viewHolder = adapter.onCreateViewHolder(mockParent, 0) adapter.onBindViewHolder(viewHolder, 1) // 绑定第2个item(Banana) // 6. 模拟点击itemView mockItemView.performClick() // 7. 验证回调是否被正确调用,参数是Banana verify(mockClickCallback).invoke("Banana") } }
2. 测试数据源更新
如果你的适配器有更新数据的方法,比如:
fun updateItems(newItems: List<String>) { itemList = newItems notifyDataSetChanged() }
测试这个方法的逻辑:
@Test fun `update items changes adapter item count`() { val initialItems = listOf("A", "B") val adapter = MyAdapter(initialItems, {}) assertEquals(2, adapter.itemCount) val newItems = listOf("X", "Y", "Z") adapter.updateItems(newItems) assertEquals(3, adapter.itemCount) }
四、如果一定要测Activity和RecyclerView的交互
如果你想验证Activity是否正确给RecyclerView设置了适配器,建议用Robolectric(而不是纯Mockito),因为它能模拟Android环境,创建真实的Activity和View实例,避免mock系统组件的空指针:
import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.LooperMode @RunWith(RobolectricTestRunner::class) @LooperMode(LooperMode.Mode.PAUSED) class MainActivityTest { @Test fun `activity sets recycler view adapter correctly`() { // 创建Activity实例 val activity = Robolectric.buildActivity(MainActivity::class.java).create().resume().get() // 获取RecyclerView val recyclerView = activity.findViewById<RecyclerView>(R.id.recycler_view) // 验证适配器不为空,且item数量正确 assertNotNull(recyclerView.adapter) assertEquals(5, recyclerView.adapter?.itemCount) } }
最后小贴士
- 单元测试要聚焦单一职责:适配器的逻辑就单独测适配器,别把Activity、RecyclerView都混在一起,这样测试更稳定,出问题也更容易定位
- 尽量避免mock Android系统组件(比如RecyclerView、Context),能用真实实例(比如Robolectric提供的)或者解耦依赖就尽量不用mock
内容的提问来源于stack exchange,提问作者Niraj Kulal
相关产品推荐
相关产品推荐

