Issue
[Testing] How can we write a test case for DiffUtil?
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "c", 0, 1)
How can we write a particular test case that our onBindViewHolder
has not been called for the items in the new list which are unmodified with respect to the oldList, which is basically what DiffUtil
do to make everything smooth?
Any idea on how can we listen for changes to the recycler view itself?
I am thinking to use registerAdapterDataObserver(AdapterDataObserver)
, where we can override onItemRangeChanged
and listen to the changes, but using such an approach in Testing is not good.
Solution
DiffUtil does not rebind any same item again, so according to the requirement, if this test case run with any other implementation other than DiffUtil, it will fail.
@Test
fun testAdapter_noRebinding() {
val adapter = getAdapterObject()
lateinit var dataObserver: RecyclerView.AdapterDataObserver
var doesRebind = false
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "b", 0)
ActivityScenario.launch(AdapterActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
dataObserver = object: RecyclerView.AdapterDataObserver(){
override fun onChanged() {
doesRebind = true
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
assertThat(positionStart).isEqualTo(0)
assertThat(itemCount).isEqualTo(3)
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
doesRebind = true
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
doesRebind = true
}
}
adapter.registerAdapterDataObserver(dataObserver)
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = oldList
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = newList
}
scenario.onActivity { activity ->
assertThat(doesRebind).isEqualTo(false)
adapter.unregisterAdapterDataObserver(dataObserver)
}
}
}
Update
Added test cases using Mockito, but these test cases are only passing on robolectric not on espresso
@Test
fun testBindableAdapter_incomingSameData_noRebindingShouldHappen() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 3)
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, never()).onChanged()
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeRemoved(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_insertOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(INT_VALUE_1, STR_VALUE_0, STR_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(2, 1, null)
verify(fakeObserver, times(1)).onItemRangeChanged(0, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_updateOneItemContent_verifyOneItemUpdated() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(1, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
Error's with espresso
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeRemoved(0, 1);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem$$inlined$use$lambda$1.perform(BindableAdapterTest.kt:303)
Actually, there were zero interactions with this mock.
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeChanged(
2,
1,
null
);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList$$inlined$use$lambda$1.perform(BindableAdapterTest.kt:349)
Actually, there were zero interactions with this mock.
Answered By - Akshay Nandwana
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.