Skip to content

Commit

Permalink
Merge pull request #34 from f-lab-edu/feature/explore-test
Browse files Browse the repository at this point in the history
Feature/explore viewmodel 테스트
  • Loading branch information
Guri999 authored Jan 11, 2025
2 parents 6429308 + ef1fdcb commit 8b56145
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import kr.co.convention.implementations
import kr.co.convention.libs
import kr.co.convention.testImplementations
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
Expand All @@ -21,6 +22,10 @@ class SeeDocsFeatureConventionPlugin : Plugin<Project> {
project(":core:model"),
libs.koin.compose
)

testImplementations(
project(":core:testing")
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ class SeeDocsLibraryConventionPlugin : Plugin<Project> {
libs.kotlinx.coroutines.android
)
testImplementations(
kotlin("test")
kotlin("test"),
)
androidTestImplementations(
kotlin("test")
kotlin("test"),
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {
}

dependencies {
api(projects.core.model)
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.database)
}
1 change: 1 addition & 0 deletions core/testing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
17 changes: 17 additions & 0 deletions core/testing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import kr.co.convention.setNamespace

plugins {
alias(libs.plugins.seedocs.library)
}

setNamespace("core.testing")

dependencies {
api(libs.mockk)
api(libs.kotlinx.coroutines.test)
api(libs.mockk.android)
api(libs.koin.test)
api(libs.koin.junit)
api(projects.core.data)
api(libs.turbine)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.co.testing.repository

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kr.co.data.repository.RecentRepository
import kr.co.model.FileInfo

class TestRecentRepository: RecentRepository {

private val recentFilesFlow: MutableStateFlow<List<FileInfo>> =
MutableStateFlow(emptyList())

override suspend fun insert(recentFile: FileInfo) {
recentFilesFlow.update { it + recentFile }
}

override fun get(): Flow<List<FileInfo>> =
recentFilesFlow

override suspend fun delete(recentFile: FileInfo) {
recentFilesFlow.update { it - recentFile }
}
}
19 changes: 19 additions & 0 deletions core/testing/src/main/java/kr/co/testing/rule/CoroutineTestRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.co.testing.rule

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description

class CoroutineTestRule : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(UnconfinedTestDispatcher())
}

override fun finished(description: Description?) {
Dispatchers.resetMain()
}
}
5 changes: 4 additions & 1 deletion feature/explore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ plugins {
alias(libs.plugins.seedocs.library.compose)
}

setNamespace("feature.explore")
setNamespace("feature.explore")
dependencies {
implementation(libs.firebase.crashlytics.buildtools)
}
9 changes: 8 additions & 1 deletion feature/explore/src/main/java/kr/co/di/ExploreModule.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package kr.co.di

import kr.co.explore.ExploreViewModel
import kr.co.util.FileManagerImpl
import kr.co.util.FileManager
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module

val exploreModule =
module {
viewModel {
ExploreViewModel(
get()
get(),
get<FileManager>(),
)
}

single<FileManager> {
FileManagerImpl()
}
}
3 changes: 1 addition & 2 deletions feature/explore/src/main/java/kr/co/explore/ExploreScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kr.co.model.ExploreSideEffect
import kr.co.model.ExploreUiIntent
import kr.co.model.ExploreUiState
import kr.co.model.FileInfo
import kr.co.seedocs.feature.explore.R
import kr.co.ui.theme.SeeDocsTheme
import kr.co.ui.theme.Theme
import kr.co.ui.util.LaunchIntentHandler
import kr.co.ui.util.LaunchSideEffect
import kr.co.ui.widget.FileBox
import kr.co.util.DEFAULT_STORAGE
import kr.co.util.FileManagerImpl.Companion.DEFAULT_STORAGE
import kr.co.widget.FolderBox
import org.koin.androidx.compose.koinViewModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import kr.co.model.ExploreUiIntent
import kr.co.model.ExploreUiState
import kr.co.model.FileInfo
import kr.co.ui.base.BaseMviViewModel
import kr.co.util.readPDFOrDirectory
import kr.co.util.FileManager

internal class ExploreViewModel(
private val recentRepository: RecentRepository,
private val fileManager: FileManager,
) :
BaseMviViewModel<ExploreUiState, ExploreUiIntent, ExploreSideEffect>(ExploreUiState.INIT) {

Expand All @@ -26,7 +27,7 @@ internal class ExploreViewModel(
copy(path = path)
}

readPDFOrDirectory(path).partition { it.isDirectory }.let { (folders, files) ->
fileManager.readPDFOrDirectory(path).partition { it.isDirectory }.let { (folders, files) ->
reduce {
copy(
folders = folders,
Expand Down
7 changes: 7 additions & 0 deletions feature/explore/src/main/java/kr/co/util/FileManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.co.util

import kr.co.model.FileInfo

internal fun interface FileManager {
suspend fun readPDFOrDirectory(path: String): List<FileInfo>
}
44 changes: 44 additions & 0 deletions feature/explore/src/main/java/kr/co/util/FileManagerImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package kr.co.util

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kr.co.model.FileInfo
import java.io.File
import java.nio.file.Files
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
import java.time.LocalDateTime
import java.time.ZoneId

internal class FileManagerImpl: FileManager {
override suspend fun readPDFOrDirectory(
path: String,
): List<FileInfo> = withContext(Dispatchers.IO) {
File(path).listFiles()?.filter { !it.isHidden && (it.isDirectory || it.extension == PDF) }
?.map {
val attributes = getFileAttributes(it)
FileInfo(
name = it.name,
path = it.path,
type = FileInfo.Type.from(it.extension),
isDirectory = it.isDirectory,
size = it.length(),
isHidden = it.isHidden,
createdAt = attributes.creationTime().toLocalDateTime(),
lastModified = attributes.lastModifiedTime().toLocalDateTime(),
)
} ?: emptyList()
}

private fun getFileAttributes(file: File): BasicFileAttributes =
Files.readAttributes(file.toPath(), BasicFileAttributes::class.java)

private fun FileTime.toLocalDateTime(): LocalDateTime =
LocalDateTime.ofInstant(this.toInstant(), ZoneId.systemDefault())

companion object {
internal const val DEFAULT_STORAGE = "/storage/emulated/0"

private const val PDF = "pdf"
}
}
40 changes: 0 additions & 40 deletions feature/explore/src/main/java/kr/co/util/FileUtil.kt

This file was deleted.

120 changes: 120 additions & 0 deletions feature/explore/src/test/kotlin/kr/co/explore/ExploreViewModelTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package kr.co.explore

import app.cash.turbine.test
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runTest
import kr.co.model.ExploreSideEffect
import kr.co.model.ExploreUiIntent
import kr.co.model.FileInfo
import kr.co.model.FileInfo.Type.PDF
import kr.co.testing.repository.TestRecentRepository
import kr.co.testing.rule.CoroutineTestRule
import kr.co.util.FileManager
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.time.LocalDateTime
import kotlin.test.assertEquals


class ExploreViewModelTest {

@get: Rule
val coroutineTestRule = CoroutineTestRule()

private lateinit var viewModel: ExploreViewModel

private val recentRepository = TestRecentRepository()

@MockK
private lateinit var fileManager: FileManager

@Before
fun setup() {
MockKAnnotations.init(this)
viewModel = ExploreViewModel(recentRepository, fileManager)
}

@Test
fun `Given a path when Init intent is handled then state is updated`() = runTest {
val path = "/path"
val folders = listOf(
FOLDER_DUMMY
)
val files = listOf(
PDF_DUMMY,
PDF_DUMMY
)

coEvery { fileManager.readPDFOrDirectory(path) } returns folders + files

viewModel.handleIntent(ExploreUiIntent.Init(path))

viewModel.uiState.test {
val state = awaitItem()
assert(state.path == path)
assertEquals(state.files.size, files.size)
assertEquals(state.folders.size, folders.size)
assert(state.folders == folders)
assert(state.files == files)
}
}

@Test
fun `Given a file when ClickFile intent is handled then navigate to pdf`() = runTest {
val file = PDF_DUMMY

recentRepository.insert(file)

viewModel.handleIntent(ExploreUiIntent.ClickFile(file))

recentRepository.insert(file)

viewModel.sideEffect.test {
awaitItem().also {
assert(it is ExploreSideEffect.NavigateToPdf)
assert((it as ExploreSideEffect.NavigateToPdf).path == file.path)
}
}
}

@Test
fun `Given a folder when ClickFolder intent is handled then navigate to folder`() = runTest {
val folder = FOLDER_DUMMY

viewModel.handleIntent(ExploreUiIntent.ClickFolder(folder))

viewModel.sideEffect.test {
awaitItem().also {
assert(it is ExploreSideEffect.NavigateToFolder)
assert((it as ExploreSideEffect.NavigateToFolder).path == folder.path)
}
}
}

companion object {
val PDF_DUMMY = FileInfo(
name = "DUMMY.pdf",
path = "",
type = PDF,
isDirectory = false,
isHidden = false,
size = 0,
createdAt = LocalDateTime.now(),
lastModified = LocalDateTime.now()
)

val FOLDER_DUMMY = FileInfo(
name = "DUMMY",
path = "",
type = PDF,
isDirectory = true,
isHidden = false,
size = 0,
createdAt = LocalDateTime.now(),
lastModified = LocalDateTime.now()
)
}
}
Loading

0 comments on commit 8b56145

Please sign in to comment.