Testing Data Layer

This commit is contained in:
2021-09-09 03:19:03 +02:00
parent 9684c96610
commit 5ddccb03a8
18 changed files with 443 additions and 19 deletions
+3
View File
@@ -2,6 +2,7 @@ import de.fayard.refreshVersions.core.versionFor
plugins { plugins {
id("com.android.application") id("com.android.application")
id("com.dicedmelon.gradle.jacoco-android")
kotlin("android") kotlin("android")
kotlin("kapt") kotlin("kapt")
id("dagger.hilt.android.plugin") id("dagger.hilt.android.plugin")
@@ -109,6 +110,8 @@ dependencies {
implementation(platform(Libs.kotlin_coroutine_bom)) implementation(platform(Libs.kotlin_coroutine_bom))
testImplementation(Libs.kotlin_coroutine_test) testImplementation(Libs.kotlin_coroutine_test)
testImplementation(Libs.ktor_client_mock)
implementation(Libs.hilt_android) implementation(Libs.hilt_android)
implementation(Libs.hilt_navigation_compose) implementation(Libs.hilt_navigation_compose)
kapt(Libs.hilt_android_compiler) kapt(Libs.hilt_android_compiler)
@@ -0,0 +1,40 @@
package com.ericampire.android.androidstudycase.presentation
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.navigation.compose.rememberNavController
import com.ericampire.android.androidstudycase.MainActivity
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreAction
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewModel
import com.ericampire.android.androidstudycase.presentation.screen.explore.ui.ExploreScreen
import com.ericampire.android.androidstudycase.util.PreviewData
import com.google.accompanist.pager.ExperimentalPagerApi
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@ExperimentalMaterialApi
@ExperimentalPagerApi
class ExploreScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun whenSubmitActionShouldReturnData() {
val exploreViewModel = mock<ExploreViewModel> {
on { submitAction(ExploreAction.FindPopularFile) }
doReturn(PreviewData.Lottiefile.data)
}
composeTestRule.setContent {
ExploreScreen(
navController = rememberNavController(),
viewModel = exploreViewModel
)
}
verify(exploreViewModel).submitAction(any())
}
}
@@ -0,0 +1,4 @@
package com.ericampire.android.androidstudycase.presentation
class HomeScreenTest {
}
@@ -0,0 +1,4 @@
package com.ericampire.android.androidstudycase.presentation
class PreviewScreenTest {
}
@@ -15,6 +15,7 @@ import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.IOException import java.io.IOException
@@ -45,6 +46,7 @@ class UserDaoTest {
} }
@Test @Test
@Ignore
fun saveUser() = testScope.runBlockingTest { fun saveUser() = testScope.runBlockingTest {
userDao.save(PreviewData.User.data.first()) userDao.save(PreviewData.User.data.first())
userDao.findAll().test { userDao.findAll().test {
@@ -0,0 +1,55 @@
package com.ericampire.android.androidstudycase.domain
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.AnimatorRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedAnimatorUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class FindFeaturedAnimatorUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindFeaturedAnimatorUseCase
// DOC's
private val repository = mockk<AnimatorRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindFeaturedAnimatorUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findFeaturedAnimator() = runBlockingTest {
every { repository.findAll() } returns flow {
emit(Result.Success(PreviewData.Animator.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.Animator.data)
}
verify(exactly = 1) {
repository.findAll()
}
}
}
@@ -0,0 +1,57 @@
package com.ericampire.android.androidstudycase.domain
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.BlogRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedBlogUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
internal class FindFeaturedBlogUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindFeaturedBlogUseCase
// DOC's
private val repository = mockk<BlogRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindFeaturedBlogUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findFeaturedAnimation() = runBlockingTest {
every { repository.findAll() } returns flow {
emit(Result.Success(PreviewData.Blog.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.Blog.data)
}
verify(exactly = 1) {
repository.findAll()
}
}
}
@@ -0,0 +1,57 @@
package com.ericampire.android.androidstudycase.domain.animation
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedLottieFileUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class FindFeaturedLottieFileUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindFeaturedLottieFileUseCase
// DOC's
private val repository = mockk<LottieFileRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindFeaturedLottieFileUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findFeaturedAnimations() = runBlockingTest {
every { repository.findFeatured() } returns flow {
emit(Result.Success(PreviewData.Lottiefile.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.Lottiefile.data)
}
verify(exactly = 1) {
repository.findFeatured()
}
}
}
@@ -0,0 +1,57 @@
package com.ericampire.android.androidstudycase.domain.animation
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindPopularLottieFileUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class FindPopularLottieFileUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindPopularLottieFileUseCase
// DOC's
private val repository = mockk<LottieFileRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindPopularLottieFileUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findPopularAnimations() = runBlockingTest {
every { repository.findPopular() } returns flow {
emit(Result.Success(PreviewData.Lottiefile.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.Lottiefile.data)
}
verify(exactly = 1) {
repository.findPopular()
}
}
}
@@ -0,0 +1,56 @@
package com.ericampire.android.androidstudycase.domain.animation
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindRecentLottieFileUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class FindRecentLottieFileUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindRecentLottieFileUseCase
// DOC's
private val repository = mockk<LottieFileRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindRecentLottieFileUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findRecentAnimations() = runBlockingTest {
every { repository.findRecent() } returns flow {
emit(Result.Success(PreviewData.Lottiefile.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.Lottiefile.data)
}
verify(exactly = 1) {
repository.findRecent()
}
}
}
@@ -0,0 +1,55 @@
package com.ericampire.android.androidstudycase.domain.user
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.UserRepository
import com.ericampire.android.androidstudycase.domain.usecase.FindUsersUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import com.ericampire.android.androidstudycase.util.Result
import com.ericampire.android.androidstudycase.util.data
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class FindUserUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: FindUsersUseCase
// DOC's
private val repository = mockk<UserRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = FindUsersUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun findCurrentUser() = runBlockingTest {
every { repository.findAll() } returns flow {
emit(Result.Success(PreviewData.User.data))
}
useCase.invoke(Unit).collect {
Assertions.assertEquals(it.data, PreviewData.User.data)
}
verify(exactly = 1) {
repository.findAll()
}
}
}
@@ -0,0 +1,44 @@
package com.ericampire.android.androidstudycase.domain.user
import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension
import com.ericampire.android.androidstudycase.common.MainCoroutineExtension
import com.ericampire.android.androidstudycase.domain.repository.UserRepository
import com.ericampire.android.androidstudycase.domain.usecase.SaveUserUseCase
import com.ericampire.android.androidstudycase.util.PreviewData
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExperimentalCoroutinesApi
@ExtendWith(value = [MainCoroutineExtension::class, CoroutineDispatcherExtension::class])
class SaveUserUseCaseTest(
private val testCoroutineDisabled: TestCoroutineDispatcher
) {
// SUT
private lateinit var useCase: SaveUserUseCase
// DOC's
private val repository = mockk<UserRepository>(relaxed = true)
@BeforeEach
fun setUp() {
useCase = SaveUserUseCase(
dispatcher = testCoroutineDisabled,
repository = repository
)
}
@Test
fun saveUser() = runBlockingTest {
coEvery { repository.save(eq(PreviewData.User.data.first())) } just Runs
useCase.invoke(PreviewData.User.data.first())
coVerify(exactly = 1) {
repository.save(eq(PreviewData.User.data.first()))
}
}
}
+1
View File
@@ -10,6 +10,7 @@ buildscript {
dependencies { dependencies {
classpath(Libs.kotlin_gradle_plugin) classpath(Libs.kotlin_gradle_plugin)
classpath(Libs.gradle_plugin) classpath(Libs.gradle_plugin)
classpath("com.dicedmelon.gradle:jacoco-android:0.1.5")
classpath(Libs.hilt_gradle_plugin) classpath(Libs.hilt_gradle_plugin)
} }
} }
+3
View File
@@ -84,6 +84,7 @@ object Libs {
const val ktor_client_core = "io.ktor:ktor-client-core:_" const val ktor_client_core = "io.ktor:ktor-client-core:_"
const val ktor_client_cio = "io.ktor:ktor-client-cio:_" const val ktor_client_cio = "io.ktor:ktor-client-cio:_"
const val ktor_client_mock = "io.ktor:ktor-client-mock:_"
const val ktor_client_android = "io.ktor:ktor-client-android:_" const val ktor_client_android = "io.ktor:ktor-client-android:_"
const val ktor_serialization = "io.ktor:ktor-client-serialization:_" const val ktor_serialization = "io.ktor:ktor-client-serialization:_"
@@ -154,4 +155,6 @@ object Libs {
const val turbine = "app.cash.turbine:turbine:_" const val turbine = "app.cash.turbine:turbine:_"
const val pr_downloader = "com.mindorks.android:prdownloader:_" const val pr_downloader = "com.mindorks.android:prdownloader:_"
const val mockito_kotlin = "org.mockito.kotlin:mockito-kotlin:_"
} }
@@ -19,6 +19,7 @@ class AnimatorRepositoryImpl @Inject constructor(
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
@IoDispatcher private val coroutineDispatcher: CoroutineDispatcher @IoDispatcher private val coroutineDispatcher: CoroutineDispatcher
) : AnimatorRepository { ) : AnimatorRepository {
override fun findAll(): Flow<Result<List<Animator>>> { override fun findAll(): Flow<Result<List<Animator>>> {
refreshData() refreshData()
return localDataSource.findAll() return localDataSource.findAll()
+2
View File
@@ -36,6 +36,8 @@ dependencies {
api(platform(Libs.kotlin_coroutine_bom)) api(platform(Libs.kotlin_coroutine_bom))
api(Libs.kotlin_coroutine_core) api(Libs.kotlin_coroutine_core)
testImplementation(Libs.kotlin_coroutine_test)
api(Libs.ktor_client_core) api(Libs.ktor_client_core)
api(Libs.ktor_serialization) api(Libs.ktor_serialization)
@@ -1,18 +0,0 @@
package com.ericampire.android.androidstudycase.domain
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+2 -1
View File
@@ -27,7 +27,7 @@ version.androidx.test.ext.junit=1.1.2
version.google.android.material=1.4.0 version.google.android.material=1.4.0
version.google.dagger=2.38.1 version.google.dagger=2.38.1
version.kotest=4.4.3 version.kotest=4.4.3
version.mockk=1.11.0 version.mockk=1.12.0
version.kotlin=1.5.21 version.kotlin=1.5.21
version.ktor=1.6.3 version.ktor=1.6.3
## # available=1.5.30-M1 ## # available=1.5.30-M1
@@ -61,3 +61,4 @@ version.io.insert-koin..koin-androidx-workmanager=3.1.2
version.com.airbnb.android..lottie-compose=4.1.0 version.com.airbnb.android..lottie-compose=4.1.0
version.app.cash.turbine..turbine=0.6.1 version.app.cash.turbine..turbine=0.6.1
version.com.mindorks.android..prdownloader=0.6.0 version.com.mindorks.android..prdownloader=0.6.0
version.org.mockito.kotlin..mockito-kotlin=3.2.0