gpt4 book ai didi

android - 如何创建用于测试的对象的 PagedList?

转载 作者:塔克拉玛干 更新时间:2023-11-01 21:27:15 28 4
gpt4 key购买 nike

我一直在使用 Google 的 arch 库,但是使测试变得困难的一件事是使用 PagedList .

对于此示例,我使用存储库模式并从 API 或网络返回详细信息。

所以在 ViewModel 中我调用了这个接口(interface)方法:

override fun getFoos(): Observable<PagedList<Foo>>

然后存储库将使用 RxPagedListBuilder创建Observable这是 PagedList 类型:

 override fun getFoos(): Observable<PagedList<Foo>> =
RxPagedListBuilder(database.fooDao().selectAll(), PAGED_LIST_CONFIG).buildObservable()

我希望能够为测试设置这些返回 PagedList<Foo> 的方法的返回值.类似于

when(repository.getFoos()).thenReturn(Observable.just(TEST_PAGED_LIST_OF_FOOS)

两个问题:

  1. 这可能吗?
  2. 如何创建 PagedList<Foo>

我的目标是以更端到端的方式进行验证(例如确保在屏幕上显示正确的 Foos 列表)。 fragment/Activity/ View 是观察 PagedList<Foo> 的那个来自 ViewModel。

最佳答案

分页 3

Paging 3 库提供了一个构建器方法 PagingData.from(someList) .

分页 2

使用模拟 DataSource.Factory 将列表转换为 PagedList。

@saied89分享了这个solution在这个googlesamples/android-architecture-components问题。我在 Coinverse Open App 中实现了模拟的 PagedList。为了使用 Kotlin、JUnit 5、MockK 和 AssertJ 库对 ViewModel 进行本地单元测试。

为了观察 PagedList 中的 LiveData,我使用了 Jose Alcérreca's implementation来自 LiveDataSample sample appgetOrAwaitValue在 Google 的 Android 架构组件示例下。

asPagedList 扩展函数在下面的示例测试 ContentViewModelTest.kt 中实现。

PagedListTestUtil.kt


import android.database.Cursor
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.room.paging.LimitOffsetDataSource
import io.mockk.every
import io.mockk.mockk

fun <T> List<T>.asPagedList() = LivePagedListBuilder<Int, T>(createMockDataSourceFactory(this),
Config(enablePlaceholders = false,
prefetchDistance = 24,
pageSize = if (size == 0) 1 else size))
.build().getOrAwaitValue()

private fun <T> createMockDataSourceFactory(itemList: List<T>): DataSource.Factory<Int, T> =
object : DataSource.Factory<Int, T>() {
override fun create(): DataSource<Int, T> = MockLimitDataSource(itemList)
}

private val mockQuery = mockk<RoomSQLiteQuery> {
every { sql } returns ""
}

private val mockDb = mockk<RoomDatabase> {
every { invalidationTracker } returns mockk(relaxUnitFun = true)
}

class MockLimitDataSource<T>(private val itemList: List<T>) : LimitOffsetDataSource<T>(mockDb, mockQuery, false, null) {
override fun convertRows(cursor: Cursor?): MutableList<T> = itemList.toMutableList()
override fun countItems(): Int = itemList.count()
override fun isInvalid(): Boolean = false
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) { /* Not implemented */ }

override fun loadRange(startPosition: Int, loadCount: Int) =
itemList.subList(startPosition, startPosition + loadCount).toMutableList()

override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
callback.onResult(itemList, 0)
}
}

LiveDataTestUtil.kt


import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

/**
* Gets the value of a [LiveData] or waits for it to have one, with a timeout.
*
* Use this extension from host-side (JVM) tests. It's recommended to use it alongside
* `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
*/
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
this.removeObserver(observer)
throw TimeoutException("LiveData value was never set.")
}
@Suppress("UNCHECKED_CAST")
return data as T
}

ContentViewModelTest.kt

    ...
import androidx.paging.PagedList
import com.google.firebase.Timestamp
import io.mockk.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(InstantExecutorExtension::class)
class ContentViewModelTest {
val timestamp = getTimeframe(DAY)

@BeforeAll
fun beforeAll() {
mockkObject(ContentRepository)
}

@BeforeEach
fun beforeEach() {
clearAllMocks()
}

@AfterAll
fun afterAll() {
unmockkAll()
}

@Test
fun `Feed Load`() {
val content = Content("85", 0.0, Enums.ContentType.NONE, Timestamp.now(), "",
"", "", "", "", "", "", MAIN,
0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0)
every {
getMainFeedList(any(), any())
} returns liveData {
emit(Lce.Content(
ContentResult.PagedListResult(
pagedList = liveData {emit(listOf(content).asPagedList())},
errorMessage = ""))
}
val contentViewModel = ContentViewModel(ContentRepository)
contentViewModel.processEvent(ContentViewEvent.FeedLoad(MAIN, DAY, timestamp, false))
assertThat(contentViewModel.feedViewState.getOrAwaitValue().contentList.getOrAwaitValue()[0])
.isEqualTo(content)
assertThat(contentViewModel.feedViewState.getOrAwaitValue().toolbar).isEqualTo(
ToolbarState(
visibility = GONE,
titleRes = app_name,
isSupportActionBarEnabled = false))
verify {
getMainFeedList(any(), any())
}
confirmVerified(ContentRepository)
}
}

InstantExecutorExtension.kt

在使用 LiveData 时,这是 JUnit 5 所必需的,以确保观察者不在主线程上。下面是Jeroen Mols' implementation .

    import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext

class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}

override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}

关于android - 如何创建用于测试的对象的 PagedList?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50435770/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com