From c5841401279dc21f1884ae905dc5585814e15a2d Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Tue, 28 Nov 2023 16:26:11 +0900 Subject: [PATCH 1/4] refactor: change deprecated function in uploadEmoji method --- .../repositories/remote/EmojiRepository.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt index 97faabff..e5d0c71c 100644 --- a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt +++ b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt @@ -16,7 +16,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody -import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import retrofit2.HttpException import retrofit2.Response import java.io.File @@ -52,32 +53,31 @@ class EmojiRepositoryImpl @Inject constructor( override suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean { val emojiDtoJson = Gson().toJson(emojiDto) - val emojiDtoRequestBody = RequestBody.create("application/json".toMediaTypeOrNull(), emojiDtoJson) + val emojiDtoRequestBody = emojiDtoJson.toRequestBody("application/json".toMediaTypeOrNull()) - val videoFileRequestBody = RequestBody.create("video/mp4".toMediaTypeOrNull(), videoFile) + val videoFileRequestBody = videoFile.asRequestBody("video/mp4".toMediaTypeOrNull()) val videoFileMultipartBody = MultipartBody.Part.createFormData("file", videoFile.name, videoFileRequestBody) val thumbnailFile = createVideoThumbnail(context, videoFile) - - val thumbnailRequestBody = RequestBody.create("image/jpg".toMediaTypeOrNull(), - thumbnailFile!! - ) - val thumbnailMultipartBody = MultipartBody.Part.createFormData("thumbnail", thumbnailFile?.name, thumbnailRequestBody) + val thumbnailRequestBody = thumbnailFile!! + .asRequestBody("image/jpg".toMediaTypeOrNull()) + val thumbnailMultipartBody = MultipartBody.Part.createFormData("thumbnail", + thumbnailFile.name, thumbnailRequestBody) return try { emojiApi.uploadEmoji(videoFileMultipartBody, thumbnailMultipartBody, emojiDtoRequestBody) true } catch (e: IOException) { - Log.d("EmojiRepository", "IOException") + Log.e("EmojiRepository", "IOException") false } catch (e: HttpException) { - Log.d("EmojiRepository", "HttpException") + Log.e("EmojiRepository", "HttpException") false } catch (e: Exception) { - Log.d("EmojiRepository", e.message.toString()) + Log.e("EmojiRepository", "Unknown Exception: ${e.message}") false } } @@ -94,7 +94,7 @@ class EmojiRepositoryImpl @Inject constructor( TODO("Not yet implemented") } - private fun createVideoThumbnail(context: Context, videoFile: File): File? { + fun createVideoThumbnail(context: Context, videoFile: File): File? { val retriever = MediaMetadataRetriever() try { retriever.setDataSource(videoFile.absolutePath) @@ -109,8 +109,7 @@ class EmojiRepositoryImpl @Inject constructor( return thumbnailFile } } catch (e: Exception) { - Log.d("create_TN", "ERROR...") - e.printStackTrace() + Log.e("EmojiRepository_create_TN", "ERROR creating thumbnail: ${e.message?:"Unknown error"}") } finally { retriever.release() } From 21dbea324d3e904da267742aebbeaef6b399478e Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Tue, 28 Nov 2023 16:27:37 +0900 Subject: [PATCH 2/4] fix: apply create thumbnail feature to emoji repository test script --- .../remote/EmojiRepositoryImplTest.kt | 90 ++++++++++++++++--- .../remote/PostRepositoryImplTest.kt | 2 +- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt index e9e25d2c..a2d7de37 100644 --- a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt +++ b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt @@ -1,11 +1,14 @@ package com.goliath.emojihub.repositories.remote +import androidx.paging.testing.asSnapshot import com.goliath.emojihub.data_sources.api.EmojiApi import com.goliath.emojihub.mockLogClass +import com.goliath.emojihub.models.EmojiDto import com.goliath.emojihub.models.UploadEmojiDto import retrofit2.Response import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.spyk @@ -24,15 +27,42 @@ import java.lang.Exception @RunWith(JUnit4::class) class EmojiRepositoryImplTest { private val emojiApi = mockk() - private val emojiRepositoryImpl = EmojiRepositoryImpl(emojiApi) + private val context = mockk() + private val emojiRepositoryImpl = EmojiRepositoryImpl(emojiApi, context) + private val sampleEmojiDto = EmojiDto( + createdBy = "channn", + createdAt = "2023-11-24 14:25:05", + savedCount = 1600, + videoLink = "https://storage.googleapis.com/emojihub-e2023.appspot.com/uu_2023-11-24%2014%3A25%3A05.mp4?GoogleAccessId=firebase-adminsdk-zynbm@emojihub-e2023.iam.gserviceaccount.com&Expires=1709443506&Signature=I%2BNRJSZ7nYtmrWs%2Fjv4uVAeW8%2BfHGF6GeV0pZRE4Sp5gCFuXLXBTKpgRBl1j2F%2BSSUStSqvBlktHZofZznGHWtsMYHQ99%2Bv7wcenqZweSWSmzse4s9sKAOkykn7pB9EMnFgax4VqGK4U5ey5HNSCKsjyNa5ZqDH8%2BqF%2FcIjQ3huChDMB2Xw1InaHUve0syvW6uz%2BeooDLo2nkGxdtElsDtomq2cAUMgk7nRNIYciYLGJ%2FsrscW7%2FXfD3rn%2BH3EM9z5S9DHKHWiEmh1xf0wpTtDsXom7p14XnZunnnOxpNO5OMFJi2x1kxZBFVc7U88V19eTmasWxdGV5TZipfN2ZMA%3D%3D", + thumbnailLink = "https://storage.googleapis.com/emojihub-e2023.appspot.com/uu_2023-11-24%2014%3A25%3A05.jpeg?GoogleAccessId=firebase-adminsdk-zynbm@emojihub-e2023.iam.gserviceaccount.com&Expires=1709443506&Signature=lZK4otdQOXBVKz3EeOEgpSqAH5QE3U6KuTz8bo5RwYQ463i0cBEx44zVPJO3dIP%2B3%2FdKkBbJy%2BzIBogKAKUl5jLyP9FwInOZChspQOuI8zp%2FKivvEZImPnoG2C1UiiwB03tHYq0tWEhgj76BB4SarWRtZY4xRZhuVvuJg9%2FNV%2B5XZ7%2BGGjLbzfjc5rA45iwWQGPfgQN0%2FKJsdTieNb5%2F6%2B5QHW4pq7QLxYAGqvea5X6VY1JcUjXU0iZ%2FfI16L%2F1cFZAMPDPNPxC2bbllFH6vkOdb3qKuvGm0M3Y99GCLTv%2BAiObbBCs13AgmBO1OngrBV4db4zNnjUZOtB0rPRgyFw%3D%3D", + id = "0ZF0MFHOV7974YTV3SBN", + label = "love it", + unicode = "U+2764 U+FE0F" + ) @Before fun setUp() { mockLogClass() } -// @Test - fun fetchEmojiList() { - TODO("Implement after applying pagination") + @Test + fun fetchEmojiList_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.fetchEmojiList(any(), any(), any()) + } returns Response.success(sampleEmojiDtoList) + // when + val fetchedEmojiPagingDataFlow = runBlocking { emojiRepositoryImpl.fetchEmojiList() } + val fetchedEmojiDtoList = runBlocking { fetchedEmojiPagingDataFlow.asSnapshot() } + // then + coVerify(exactly = 2) { emojiApi.fetchEmojiList(any(), any(), any()) } + runBlocking { + assertEquals(expectedFetchedEmojiDtoList.size, fetchedEmojiDtoList.size) + assertEquals(expectedFetchedEmojiDtoList, fetchedEmojiDtoList) + } } // @Test @@ -47,14 +77,23 @@ class EmojiRepositoryImplTest { val sampleVideoFile = File("sampleVideoFile") val sampleUploadEmojiDto = mockk() coEvery { - emojiApi.uploadEmoji(any(), any()) + emojiApi.uploadEmoji(any(), any(), any()) } returns Response.success(Unit) + + val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context)) + every { + emojiRepositoryImpl.createVideoThumbnail(any(), any()) + } returns File("sampleThumbnailFile") + coEvery { + emojiRepositoryImpl.uploadEmoji(any(), any()) + } answers { callOriginal() } + // when val isUploaded = runBlocking { emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto) } // then - coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any()) } + coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) } assertTrue(isUploaded) } @@ -65,14 +104,23 @@ class EmojiRepositoryImplTest { val sampleVideoFile = File("sampleVideoFile") val sampleUploadEmojiDto = mockk() coEvery { - emojiApi.uploadEmoji(any(), any()) + emojiApi.uploadEmoji(any(), any(), any()) } throws mockk() + + val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context)) + every { + emojiRepositoryImpl.createVideoThumbnail(any(), any()) + } returns File("sampleThumbnailFile") + coEvery { + emojiRepositoryImpl.uploadEmoji(any(), any()) + } answers { callOriginal() } + // when val isUploaded = runBlocking { emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto) } // then - coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any()) } + coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) } assertFalse(isUploaded) } @@ -83,14 +131,23 @@ class EmojiRepositoryImplTest { val sampleVideoFile = File("sampleVideoFile") val sampleUploadEmojiDto = mockk() coEvery { - emojiApi.uploadEmoji(any(), any()) + emojiApi.uploadEmoji(any(), any(), any()) } throws mockk() + + val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context)) + every { + emojiRepositoryImpl.createVideoThumbnail(any(), any()) + } returns File("sampleThumbnailFile") + coEvery { + emojiRepositoryImpl.uploadEmoji(any(), any()) + } answers { callOriginal() } + // when val isUploaded = runBlocking { emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto) } // then - coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any()) } + coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) } assertFalse(isUploaded) } @@ -101,14 +158,23 @@ class EmojiRepositoryImplTest { val sampleVideoFile = File("sampleVideoFile") val sampleUploadEmojiDto = mockk() coEvery { - emojiApi.uploadEmoji(any(), any()) + emojiApi.uploadEmoji(any(), any(), any()) } throws spyk() + + val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context)) + every { + emojiRepositoryImpl.createVideoThumbnail(any(), any()) + } returns File("sampleThumbnailFile") + coEvery { + emojiRepositoryImpl.uploadEmoji(any(), any()) + } answers { callOriginal() } + // when val isUploaded = runBlocking { emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto) } // then - coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any()) } + coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) } assertFalse(isUploaded) } diff --git a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/PostRepositoryImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/PostRepositoryImplTest.kt index e050f0be..b3c9f094 100644 --- a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/PostRepositoryImplTest.kt +++ b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/PostRepositoryImplTest.kt @@ -36,7 +36,7 @@ class PostRepositoryImplTest { } @Test - fun fetchPostList_returnsPagingSourceFactory() { + fun fetchPostList_returnsFlowOfPagingDataOfPostDto() { // given val numSamplePosts = 10 val samplePostDtoList = List(numSamplePosts) { samplePostDto } From 57608ac39c1d037d3bf41faf14a62b59394afcd4 Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Tue, 28 Nov 2023 16:36:16 +0900 Subject: [PATCH 3/4] feat: apply emojiList pagination --- .../emojihub/usecases/EmojiUseCaseImplTest.kt | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt index bcd23af4..4e00fa19 100644 --- a/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt +++ b/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt @@ -1,8 +1,13 @@ package com.goliath.emojihub.usecases +import androidx.paging.PagingData +import androidx.paging.map +import androidx.paging.testing.asSnapshot 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 @@ -12,6 +17,8 @@ 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.* import org.junit.Before @@ -35,9 +42,34 @@ class EmojiUseCaseImplTest { mockLogClass() } -// @Test - fun fetchEmojiList() { - TODO("Implement after pagination is implemented") + @Test + fun updateEmojiList_withSamplePagingEmojiData_updatesEmojiListStateFlow() { + // given + val samplePagingEmojiData = mockk>() + // when + runBlocking { emojiUseCaseImpl.updateEmojiList(samplePagingEmojiData) } + // then + assertEquals(samplePagingEmojiData, emojiUseCaseImpl.emojiList.value) + } + + @Test + fun fetchEmojiList_returnsFlowOfEmojiPagingData() { + // given + val sampleEmojiPagingDataFlow = spyk>>() + val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } } + coEvery { + emojiRepository.fetchEmojiList() + } returns sampleEmojiPagingDataFlow + // when + val fetchedEmojiPagingDataFlow = runBlocking { emojiUseCaseImpl.fetchEmojiList() } + // then + coVerify(exactly = 1) { emojiRepository.fetchEmojiList() } + runBlocking { + assertEquals( + sampleAnswer.asSnapshot(), + fetchedEmojiPagingDataFlow.asSnapshot() + ) + } } @Test From 5549ba485665275bcdcb8193363a1096d3d57bfb Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Tue, 28 Nov 2023 17:08:40 +0900 Subject: [PATCH 4/4] fix: delete not implemented viewModel test script --- .../emojihub/viewmodels/EmojiViewModelTest.kt | 32 ----- .../emojihub/viewmodels/PostViewModelTest.kt | 114 ------------------ 2 files changed, 146 deletions(-) delete mode 100644 android/app/src/test/java/com/goliath/emojihub/viewmodels/EmojiViewModelTest.kt delete mode 100644 android/app/src/test/java/com/goliath/emojihub/viewmodels/PostViewModelTest.kt diff --git a/android/app/src/test/java/com/goliath/emojihub/viewmodels/EmojiViewModelTest.kt b/android/app/src/test/java/com/goliath/emojihub/viewmodels/EmojiViewModelTest.kt deleted file mode 100644 index f7726697..00000000 --- a/android/app/src/test/java/com/goliath/emojihub/viewmodels/EmojiViewModelTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.goliath.emojihub.viewmodels - -import com.goliath.emojihub.usecases.EmojiUseCase -import io.mockk.spyk -import org.junit.Assert.* - -import org.junit.Test - -class EmojiViewModelTest { - private val emojiUseCase = spyk() - private val emojiViewModel = EmojiViewModel(emojiUseCase) - - @Test - fun fetchEmojiList() { - } - - @Test - fun createEmoji() { - } - - @Test - fun uploadEmoji() { - } - - @Test - fun saveEmoji() { - } - - @Test - fun unSaveEmoji() { - } -} \ No newline at end of file diff --git a/android/app/src/test/java/com/goliath/emojihub/viewmodels/PostViewModelTest.kt b/android/app/src/test/java/com/goliath/emojihub/viewmodels/PostViewModelTest.kt deleted file mode 100644 index 63068d60..00000000 --- a/android/app/src/test/java/com/goliath/emojihub/viewmodels/PostViewModelTest.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.goliath.emojihub.viewmodels - -import androidx.paging.PagingData -import com.goliath.emojihub.data_sources.ApiErrorController -import com.goliath.emojihub.models.Post -import com.goliath.emojihub.repositories.remote.PostRepository -import com.goliath.emojihub.usecases.PostUseCaseImpl -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.After -import org.junit.Assert.* -import org.junit.Before - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@RunWith(JUnit4::class) -@OptIn(ExperimentalCoroutinesApi::class) -class PostViewModelTest { - private val postRepository = mockk() - private val apiErrorController = mockk() - private val postUseCase = spyk(PostUseCaseImpl - (postRepository, apiErrorController) - ) - private val postViewModel = PostViewModel(postUseCase) - - private val testDispatcher = StandardTestDispatcher() - @Before - fun setUp() { - Dispatchers.setMain(testDispatcher) - } - @After - fun cleanUp() { - Dispatchers.resetMain() - } - - @Test - fun fetchPostList_success_updatePostList() { - // given - val samplePagingDataFlow = spyk>>() - coEvery { - postUseCase.fetchPostList() - } returns samplePagingDataFlow - // when - runBlocking { postViewModel.fetchPostList() } - // then - coVerify { postUseCase.fetchPostList() } - verify { runBlocking { postUseCase.updatePostList(any()) } } - assertEquals(samplePagingDataFlow, postViewModel.postList) - } - - @Test - fun uploadPost_withValidContent_returnsTrue() { - // given - val sampleContent = "sample content" - every { - runBlocking { - postUseCase.uploadPost(sampleContent) - } - } returns true - // when - val isUploaded = runBlocking { - postViewModel.uploadPost(sampleContent) - } - // then - verify { runBlocking { postUseCase.uploadPost(sampleContent) } } - assertTrue(isUploaded) - } - - @Test - fun uploadPost_occursError_returnsFalse() { - // given - val sampleContent = "sample content" - every { - runBlocking { - postUseCase.uploadPost(sampleContent) - } - } returns false - // when - val isUploaded = runBlocking { - postViewModel.uploadPost(sampleContent) - } - // then - verify { runBlocking { postUseCase.uploadPost(sampleContent) } } - assertFalse(isUploaded) - } - - // @Test - // No return value - fun getPostWithId() { - } - - // @Test - // No return value - fun editPost() { - } - - // @Test - // No return value - fun deletePost() { - } -} \ No newline at end of file