Skip to content

Commit

Permalink
test: add Unit tests PixabayImagePagingSourceTest.
Browse files Browse the repository at this point in the history
  • Loading branch information
azrael8576 committed Dec 19, 2023
1 parent a3eab0a commit 9f28ab4
Show file tree
Hide file tree
Showing 14 changed files with 1,181 additions and 4 deletions.
3 changes: 3 additions & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.pq.android.library)
alias(libs.plugins.pq.android.hilt)
id("kotlinx-serialization")
}

android {
Expand All @@ -18,4 +19,6 @@ dependencies {
implementation(project(":core:network"))
implementation(project(":core:model"))
implementation(project(":core:datastore"))

implementation(libs.kotlinx.serialization.json)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.paging.PagingData
import androidx.paging.map
import com.wei.picquest.core.data.model.ImageDetail
import com.wei.picquest.core.data.model.asExternalModel
import com.wei.picquest.core.data.pagingsource.PixabayImagePagingSource
import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.pagingsource.PixabayImagePagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.paging.PagingData
import androidx.paging.map
import com.wei.picquest.core.data.model.VideoDetail
import com.wei.picquest.core.data.model.asExternalModel
import com.wei.picquest.core.data.pagingsource.PixabayVideoPagingSource
import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.pagingsource.PixabayVideoPagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
Expand Down
2 changes: 2 additions & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ secrets {
}

dependencies {
implementation(project(":core:common"))

// Okhttp Interceptor
implementation(libs.okhttp.logging)

Expand Down
486 changes: 486 additions & 0 deletions core/network/src/main/assets/images_page1.json

Large diffs are not rendered by default.

486 changes: 486 additions & 0 deletions core/network/src/main/assets/images_page2.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions core/network/src/main/assets/images_page_end.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"total": 7,
"totalHits": 7,
"hits": []
}
5 changes: 5 additions & 0 deletions core/network/src/main/assets/videos_page_end.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"total": 7,
"totalHits": 7,
"hits": []
}
26 changes: 26 additions & 0 deletions core/network/src/main/java/JvmUnitTestFakeAssetManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import androidx.annotation.VisibleForTesting
import com.wei.picquest.core.network.fake.FakeAssetManager
import java.io.File
import java.io.InputStream
import java.util.Properties

/**
* This class helps with loading Android `/assets` files, especially when running JVM unit tests.
* It must remain on the root package for an easier [Class.getResource] with relative paths.
* @see <a href="https://developer.android.com/reference/tools/gradle-api/7.3/com/android/build/api/dsl/UnitTestOptions">UnitTestOptions</a>
*/
@VisibleForTesting
internal object JvmUnitTestFakeAssetManager : FakeAssetManager {
private val config =
requireNotNull(javaClass.getResource("com/android/tools/test_config.properties")) {
"""
Missing Android resources properties file.
Did you forget to enable the feature in the gradle build file?
android.testOptions.unitTests.isIncludeAndroidResources = true
""".trimIndent()
}
private val properties = Properties().apply { config.openStream().use(::load) }
private val assets = File(properties["android_merged_assets"].toString())

override fun open(fileName: String): InputStream = File(assets, fileName).inputStream()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wei.picquest.core.network.fake

import java.io.InputStream

fun interface FakeAssetManager {
fun open(fileName: String): InputStream
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.wei.picquest.core.network.fake

import JvmUnitTestFakeAssetManager
import com.wei.picquest.core.network.Dispatcher
import com.wei.picquest.core.network.PqDispatchers
import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.model.NetworkSearchImages
import com.wei.picquest.core.network.model.NetworkSearchVideos
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.jetbrains.annotations.VisibleForTesting

class FakePqNetworkDataSource(
@Dispatcher(PqDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
private val networkJson: Json,
private val assets: FakeAssetManager = JvmUnitTestFakeAssetManager,
) : PqNetworkDataSource {

private var shouldReturnErrorForImages = false

@VisibleForTesting
fun setReturnErrorForImages(shouldReturnError: Boolean) {
shouldReturnErrorForImages = shouldReturnError
}

@OptIn(ExperimentalSerializationApi::class)
override suspend fun searchImages(query: String, page: Int, perPage: Int): NetworkSearchImages =
withContext(ioDispatcher) {
if (shouldReturnErrorForImages) {
throw Exception("Fake exception for images")
}
when (page) {
0 -> assets.open(IMAGES_PAGE1_ASSET).use(networkJson::decodeFromStream)
1 -> assets.open(IMAGES_PAGE2_ASSET).use(networkJson::decodeFromStream)
else -> {
assets.open(IMAGES_PAGE_END_ASSET).use(networkJson::decodeFromStream)
}
}
}

@OptIn(ExperimentalSerializationApi::class)
override suspend fun searchVideos(query: String, page: Int, perPage: Int): NetworkSearchVideos =
withContext(ioDispatcher) {
assets.open(VIDEOS_PAGE_END_ASSET).use(networkJson::decodeFromStream)
}

companion object {
private const val IMAGES_PAGE1_ASSET = "images_page1.json"
private const val IMAGES_PAGE2_ASSET = "images_page2.json"
private const val IMAGES_PAGE_END_ASSET = "images_page_end.json"
private const val VIDEOS_PAGE_END_ASSET = "videos_page_end.json"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wei.picquest.core.data.pagingsource
package com.wei.picquest.core.network.pagingsource

import androidx.paging.PagingSource
import androidx.paging.PagingState
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wei.picquest.core.data.pagingsource
package com.wei.picquest.core.network.pagingsource

import androidx.paging.PagingSource
import androidx.paging.PagingState
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.wei.picquest.core.network.pagingsource

import JvmUnitTestFakeAssetManager
import androidx.paging.PagingSource
import com.google.common.truth.Truth.assertThat
import com.wei.picquest.core.network.fake.FakePqNetworkDataSource
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import org.junit.Before
import org.junit.Test

/**
* Unit tests for [PixabayImagePagingSource].
*
* 遵循此模型,安排、操作、斷言:
* {Arrange}{Act}{Assert}
*/
class PixabayImagePagingSourceTest {

private lateinit var fakePqNetworkDataSource: FakePqNetworkDataSource
private lateinit var pixabayImagePagingSource: PixabayImagePagingSource

private val testDispatcher = StandardTestDispatcher()

@Before
fun setUp() {
fakePqNetworkDataSource = FakePqNetworkDataSource(
ioDispatcher = testDispatcher,
networkJson = Json { ignoreUnknownKeys = true },
assets = JvmUnitTestFakeAssetManager,
)
pixabayImagePagingSource = PixabayImagePagingSource(fakePqNetworkDataSource, "testQuery")
}

@Test
fun `load should return non-empty page for first and second page`() = runTest(testDispatcher) {
// Arrange
val firstPageKey = 0
val secondPageKey = 1

// Act
val firstPageResult = pixabayImagePagingSource.load(
PagingSource.LoadParams.Refresh(
key = firstPageKey,
loadSize = 20,
placeholdersEnabled = false,
),
)
val secondPageResult = pixabayImagePagingSource.load(
PagingSource.LoadParams.Refresh(
key = secondPageKey,
loadSize = 20,
placeholdersEnabled = false,
),
)

// Assert
assertThat(firstPageResult).isInstanceOf(PagingSource.LoadResult.Page::class.java)
assertThat((firstPageResult as PagingSource.LoadResult.Page).data).isNotEmpty()
assertThat(secondPageResult).isInstanceOf(PagingSource.LoadResult.Page::class.java)
assertThat((secondPageResult as PagingSource.LoadResult.Page).data).isNotEmpty()
}

@Test
fun `load should return empty page at end of pagination`() = runTest(testDispatcher) {
// Arrange
val endPageKey = 2 // Assuming this is the end

// Act
val endPageResult = pixabayImagePagingSource.load(
PagingSource.LoadParams.Refresh(
key = endPageKey,
loadSize = 20,
placeholdersEnabled = false,
),
)

// Assert
assertThat(endPageResult).isInstanceOf(PagingSource.LoadResult.Page::class.java)
assertThat((endPageResult as PagingSource.LoadResult.Page).data).isEmpty()
}

@Test
fun `load should return error when data source throws exception`() = runTest {
// Arrange
fakePqNetworkDataSource.setReturnErrorForImages(true)

// Act
val result = pixabayImagePagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 20,
placeholdersEnabled = false,
),
)

// Assert
assertThat(result).isInstanceOf(PagingSource.LoadResult.Error::class.java)
}
}

0 comments on commit 9f28ab4

Please sign in to comment.