Skip to content

Commit

Permalink
Merge pull request #82 from snuhcs-course/test/android-emoji-view-model
Browse files Browse the repository at this point in the history
Test/android emoji view model
  • Loading branch information
yangchanhk98 authored Nov 30, 2023
2 parents 595be0e + ba9261c commit 28450e6
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 23 deletions.
30 changes: 30 additions & 0 deletions android/app/src/main/java/com/goliath/emojihub/models/Emoji.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,36 @@ class Emoji(
val unicode: String = dto.unicode
val label: String = dto.label
val id: String = dto.id

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Emoji
if (createdBy != other.createdBy) return false
if (isSaved != other.isSaved) return false
if (createdAt != other.createdAt) return false
if (savedCount != other.savedCount) return false
if (videoLink != other.videoLink) return false
if (thumbnailLink != other.thumbnailLink) return false
if (unicode != other.unicode) return false
if (label != other.label) return false
if (id != other.id) return false

return true
}
override fun hashCode(): Int {
var result = createdBy.hashCode()
result = 31 * result + isSaved.hashCode()
result = 31 * result + createdAt.hashCode()
result = 31 * result + savedCount
result = 31 * result + videoLink.hashCode()
result = 31 * result + thumbnailLink.hashCode()
result = 31 * result + unicode.hashCode()
result = 31 * result + label.hashCode()
result = 31 * result + id.hashCode()
return result
}
}

data class EmojiDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ import javax.inject.Inject
class EmojiViewModel @Inject constructor(
private val emojiUseCase: EmojiUseCase
): ViewModel() {
var videoUri: Uri = Uri.EMPTY
lateinit var videoUri: Uri
var currentEmoji: Emoji? = null
var bottomSheetContent by mutableStateOf(BottomSheetContent.EMPTY)

val emojiList = emojiUseCase.emojiList
val myCreatedEmojiList = emojiUseCase.myCreatedEmojiList
val mySavedEmojiList = emojiUseCase.mySavedEmojiList

private val _topK = 3
companion object {
private const val _topK = 3
}

fun fetchEmojiList() {
viewModelScope.launch {
Expand Down Expand Up @@ -80,6 +81,6 @@ class EmojiViewModel @Inject constructor(
}

suspend fun unSaveEmoji(id: String) {
emojiUseCase.saveEmoji(id)
emojiUseCase.unSaveEmoji(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,48 @@ class EmojiRepositoryImplTest {
}
}

@Test
fun fetchMyCreatedEmojiList_returnsFlowOfPagingDataOfEmojiDto() {
// given
val numSampleEmojis = 10
val sampleEmojiDtoList = List(numSampleEmojis) { sampleEmojiDto }
val expectedFetchedEmojiDtoList = List(numSampleEmojis*2) { sampleEmojiDto }
// *2 because of .asSnapshot() load one more time
coEvery {
emojiApi.fetchMyCreatedEmojiList(any(), any(), any())
} returns Response.success(sampleEmojiDtoList)
// when
val fetchedEmojiPagingDataFlow = runBlocking { emojiRepositoryImpl.fetchMyCreatedEmojiList() }
val fetchedEmojiDtoList = runBlocking { fetchedEmojiPagingDataFlow.asSnapshot() }
// then
coVerify(exactly = 2) { emojiApi.fetchMyCreatedEmojiList(any(), any(), any()) }
runBlocking {
assertEquals(expectedFetchedEmojiDtoList.size, fetchedEmojiDtoList.size)
assertEquals(expectedFetchedEmojiDtoList, fetchedEmojiDtoList)
}
}

@Test
fun fetchMySavedEmojiList_returnsFlowOfPagingDataOfEmojiDto() {
// given
val numSampleEmojis = 10
val sampleEmojiDtoList = List(numSampleEmojis) { sampleEmojiDto }
val expectedFetchedEmojiDtoList = List(numSampleEmojis*2) { sampleEmojiDto }
// *2 because of .asSnapshot() load one more time
coEvery {
emojiApi.fetchMySavedEmojiList(any(), any(), any())
} returns Response.success(sampleEmojiDtoList)
// when
val fetchedEmojiPagingDataFlow = runBlocking { emojiRepositoryImpl.fetchMySavedEmojiList() }
val fetchedEmojiDtoList = runBlocking { fetchedEmojiPagingDataFlow.asSnapshot() }
// then
coVerify(exactly = 2) { emojiApi.fetchMySavedEmojiList(any(), any(), any()) }
runBlocking {
assertEquals(expectedFetchedEmojiDtoList.size, fetchedEmojiDtoList.size)
assertEquals(expectedFetchedEmojiDtoList, fetchedEmojiDtoList)
}
}

// @Test
fun getEmojiWithId() {
TODO("Not yet implemented")
Expand All @@ -84,9 +126,6 @@ class EmojiRepositoryImplTest {
every {
emojiRepositoryImpl.createVideoThumbnail(any(), any())
} returns File("sampleThumbnailFile")
coEvery {
emojiRepositoryImpl.uploadEmoji(any(), any())
} answers { callOriginal() }

// when
val isUploaded = runBlocking {
Expand All @@ -111,9 +150,6 @@ class EmojiRepositoryImplTest {
every {
emojiRepositoryImpl.createVideoThumbnail(any(), any())
} returns File("sampleThumbnailFile")
coEvery {
emojiRepositoryImpl.uploadEmoji(any(), any())
} answers { callOriginal() }

// when
val isUploaded = runBlocking {
Expand All @@ -138,9 +174,6 @@ class EmojiRepositoryImplTest {
every {
emojiRepositoryImpl.createVideoThumbnail(any(), any())
} returns File("sampleThumbnailFile")
coEvery {
emojiRepositoryImpl.uploadEmoji(any(), any())
} answers { callOriginal() }

// when
val isUploaded = runBlocking {
Expand All @@ -165,9 +198,6 @@ class EmojiRepositoryImplTest {
every {
emojiRepositoryImpl.createVideoThumbnail(any(), any())
} returns File("sampleThumbnailFile")
coEvery {
emojiRepositoryImpl.uploadEmoji(any(), any())
} answers { callOriginal() }

// when
val isUploaded = runBlocking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.goliath.emojihub.usecases
import androidx.paging.PagingData
import androidx.paging.map
import androidx.paging.testing.asSnapshot
import com.goliath.emojihub.createDeterministicDummyEmojiDtoList
import com.goliath.emojihub.data_sources.ApiErrorController
import com.goliath.emojihub.mockLogClass
import com.goliath.emojihub.models.CreatedEmoji
import com.goliath.emojihub.models.Emoji
import com.goliath.emojihub.models.EmojiDto
import com.goliath.emojihub.models.UploadEmojiDto
import com.goliath.emojihub.repositories.local.X3dRepository
import com.goliath.emojihub.repositories.remote.EmojiRepository
Expand All @@ -17,7 +17,6 @@ import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
Expand Down Expand Up @@ -52,10 +51,30 @@ class EmojiUseCaseImplTest {
assertEquals(samplePagingEmojiData, emojiUseCaseImpl.emojiList.value)
}

@Test
fun updateMyCreatedEmojiList_withSamplePagingEmojiData_updatesMyCreatedEmojiListStateFlow() {
// given
val samplePagingEmojiData = mockk<PagingData<Emoji>>()
// when
runBlocking { emojiUseCaseImpl.updateMyCreatedEmojiList(samplePagingEmojiData) }
// then
assertEquals(samplePagingEmojiData, emojiUseCaseImpl.myCreatedEmojiList.value)
}

@Test
fun updateMySavedEmojiList_withSamplePagingEmojiData_updatesMySavedEmojiListStateFlow() {
// given
val samplePagingEmojiData = mockk<PagingData<Emoji>>()
// when
runBlocking { emojiUseCaseImpl.updateMySavedEmojiList(samplePagingEmojiData) }
// then
assertEquals(samplePagingEmojiData, emojiUseCaseImpl.mySavedEmojiList.value)
}

@Test
fun fetchEmojiList_returnsFlowOfEmojiPagingData() {
// given
val sampleEmojiPagingDataFlow = spyk<Flow<PagingData<EmojiDto>>>()
val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
emojiRepository.fetchEmojiList()
Expand All @@ -65,10 +84,62 @@ class EmojiUseCaseImplTest {
// then
coVerify(exactly = 1) { emojiRepository.fetchEmojiList() }
runBlocking {
assertEquals(
sampleAnswer.asSnapshot(),
fetchedEmojiPagingDataFlow.asSnapshot()
)
val sampleAnswerAsSnapshot = sampleAnswer.asSnapshot()
val fetchedEmojiPagingDataFlowAsSnapshot = fetchedEmojiPagingDataFlow.asSnapshot()
for (i in sampleAnswerAsSnapshot.indices) {
assertEquals(
sampleAnswerAsSnapshot[i],
fetchedEmojiPagingDataFlowAsSnapshot[i]
)
}
}
}

@Test
fun fetchMyCreatedEmojiList_returnsFlowOfEmojiPagingData() {
// given
val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
emojiRepository.fetchMyCreatedEmojiList()
} returns sampleEmojiPagingDataFlow
// when
val fetchedEmojiPagingDataFlow = runBlocking { emojiUseCaseImpl.fetchMyCreatedEmojiList() }
// then
coVerify(exactly = 1) { emojiRepository.fetchMyCreatedEmojiList() }
runBlocking {
val sampleAnswerAsSnapshot = sampleAnswer.asSnapshot()
val fetchedEmojiPagingDataFlowAsSnapshot = fetchedEmojiPagingDataFlow.asSnapshot()
for (i in sampleAnswerAsSnapshot.indices) {
assertEquals(
sampleAnswerAsSnapshot[i],
fetchedEmojiPagingDataFlowAsSnapshot[i]
)
}
}
}

@Test
fun fetchMySavedEmojiList_returnsFlowOfEmojiPagingData() {
// given
val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
emojiRepository.fetchMySavedEmojiList()
} returns sampleEmojiPagingDataFlow
// when
val fetchedEmojiPagingDataFlow = runBlocking { emojiUseCaseImpl.fetchMySavedEmojiList() }
// then
coVerify(exactly = 1) { emojiRepository.fetchMySavedEmojiList() }
runBlocking {
val sampleAnswerAsSnapshot = sampleAnswer.asSnapshot()
val fetchedEmojiPagingDataFlowAsSnapshot = fetchedEmojiPagingDataFlow.asSnapshot()
for (i in sampleAnswerAsSnapshot.indices) {
assertEquals(
sampleAnswerAsSnapshot[i],
fetchedEmojiPagingDataFlowAsSnapshot[i]
)
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions android/app/src/test/java/com/goliath/emojihub/utils.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
package com.goliath.emojihub

import android.util.Log
import androidx.paging.PagingData
import androidx.paging.map
import com.goliath.emojihub.models.Emoji
import com.goliath.emojihub.models.EmojiDto
import io.mockk.every
import io.mockk.mockkStatic
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

fun mockLogClass() {
mockkStatic(Log::class)
every { Log.v(any(), any()) } returns 0
every { Log.d(any(), any()) } returns 0
every { Log.i(any(), any()) } returns 0
every { Log.e(any(), any()) } returns 0
}

val dummyUsernames = listOf("channn", "doggydog", "meow_0w0", "mpunchmm", "kick_back")
val dummyUnicodes = listOf("U+1F44D", "U+1F600", "U+1F970", "U+1F60E", "U+1F621", "U+1F63A", "U+1F496", "U+1F415")
const val dummyMaxSavedCounts = 2000
fun createDeterministicDummyEmojiDtoList(listSize : Int): Flow<PagingData<EmojiDto>> {
val dummyEmojiList = mutableListOf<EmojiDto>()
for (i in 0 until listSize) {
dummyEmojiList.add(
EmojiDto(
createdBy = dummyUsernames[i % dummyUsernames.size],
createdAt = "2023.09.16",
savedCount = dummyMaxSavedCounts % (i + 1),
videoLink = "",
thumbnailLink = "",
unicode = dummyUnicodes[i % dummyUnicodes.size],
id = "1234",
label = "sample"
)
)
}
return flowOf(PagingData.from(dummyEmojiList))
}

fun createDeterministicDummyEmojiList(listSize: Int): Flow<PagingData<Emoji>> {
return createDeterministicDummyEmojiDtoList(listSize).map { it.map { dto -> Emoji(dto) } }
}
Loading

0 comments on commit 28450e6

Please sign in to comment.