From 73ae6df075602b4b34547b5964bf38c382c149ea Mon Sep 17 00:00:00 2001 From: Eric Ampire Date: Thu, 9 Sep 2021 00:20:01 +0200 Subject: [PATCH] Testing Repositories --- .../androidstudycase/ExampleUnitTest.kt | 17 --- .../common/CoroutineDispatcherExtension.kt | 25 +++++ .../common/CoroutineScopeExtension.kt | 24 +++++ .../common/MainCoroutineExtension.kt | 25 +++++ .../data/repository/AnimatorRepositoryTest.kt | 4 +- .../data/repository/BlogRepositoryTest.kt | 68 ++++++++++++ .../repository/LottieFileRepositoryTest.kt | 100 ++++++++++++++++++ .../data/repository/UserRepositoryTest.kt | 66 ++++++++++++ 8 files changed, 310 insertions(+), 19 deletions(-) delete mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/ExampleUnitTest.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineDispatcherExtension.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineScopeExtension.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/common/MainCoroutineExtension.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryTest.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryTest.kt create mode 100644 app/src/test/java/com/ericampire/android/androidstudycase/data/repository/UserRepositoryTest.kt diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/ExampleUnitTest.kt b/app/src/test/java/com/ericampire/android/androidstudycase/ExampleUnitTest.kt deleted file mode 100644 index ce2a90d..0000000 --- a/app/src/test/java/com/ericampire/android/androidstudycase/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.ericampire.android.androidstudycase - -import org.junit.Test - -import org.junit.Assert.* - -/** - * 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) - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineDispatcherExtension.kt b/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineDispatcherExtension.kt new file mode 100644 index 0000000..39eaa43 --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineDispatcherExtension.kt @@ -0,0 +1,25 @@ +package com.ericampire.android.androidstudycase.common + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +@ExperimentalCoroutinesApi +class CoroutineDispatcherExtension : ParameterResolver { + + override fun supportsParameter( + parameterContext: ParameterContext, + extensionContext: ExtensionContext? + ): Boolean { + return parameterContext.parameter.type.equals(TestCoroutineDispatcher::class.java) + } + + override fun resolveParameter( + parameterContext: ParameterContext?, + extensionContext: ExtensionContext? + ): Any { + return TestCoroutineDispatcher() + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineScopeExtension.kt b/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineScopeExtension.kt new file mode 100644 index 0000000..b9fd315 --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/common/CoroutineScopeExtension.kt @@ -0,0 +1,24 @@ +package com.ericampire.android.androidstudycase.common + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +@ExperimentalCoroutinesApi +class CoroutineScopeExtension : ParameterResolver { + override fun supportsParameter( + parameterContext: ParameterContext, + extensionContext: ExtensionContext? + ): Boolean { + return parameterContext.parameter.type.equals(TestCoroutineScope::class.java) + } + + override fun resolveParameter( + parameterContext: ParameterContext?, + extensionContext: ExtensionContext? + ): Any { + return TestCoroutineScope() + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/common/MainCoroutineExtension.kt b/app/src/test/java/com/ericampire/android/androidstudycase/common/MainCoroutineExtension.kt new file mode 100644 index 0000000..f64b7fd --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/common/MainCoroutineExtension.kt @@ -0,0 +1,25 @@ +package com.ericampire.android.androidstudycase.common + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.extension.AfterAllCallback +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtensionContext + +@ExperimentalCoroutinesApi +class MainCoroutineExtension : BeforeAllCallback, AfterAllCallback { + + private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() + + override fun beforeAll(context: ExtensionContext?) { + Dispatchers.setMain(testDispatcher) + } + + override fun afterAll(context: ExtensionContext?) { + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryTest.kt b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryTest.kt index 3e70bec..7c195b3 100644 --- a/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryTest.kt +++ b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryTest.kt @@ -61,8 +61,8 @@ class AnimatorRepositoryTest( assertEquals(it.data, PreviewData.Animator.data) } - coVerify { - remoteDataSource.findAll() + verify(exactly = 1) { remoteDataSource.findAll() } + coVerify(exactly = PreviewData.Animator.data.size) { localDataSource.save(any()) } } diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryTest.kt b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryTest.kt new file mode 100644 index 0000000..b9a1f0f --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryTest.kt @@ -0,0 +1,68 @@ +package com.ericampire.android.androidstudycase.data.repository + +import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension +import com.ericampire.android.androidstudycase.common.CoroutineScopeExtension +import com.ericampire.android.androidstudycase.common.MainCoroutineExtension +import com.ericampire.android.androidstudycase.data.datasource.blog.BlogDataSource +import com.ericampire.android.androidstudycase.util.PreviewData +import com.ericampire.android.androidstudycase.util.Result +import com.ericampire.android.androidstudycase.util.data +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.time.ExperimentalTime + +@ExperimentalCoroutinesApi +@ExtendWith( + value = [ + MainCoroutineExtension::class, + CoroutineDispatcherExtension::class, + CoroutineScopeExtension::class + ] +) +class BlogRepositoryTest( + private val coroutineScope: TestCoroutineScope, + private val dispatcher: TestCoroutineDispatcher +) { + + // SUT + private lateinit var blogRepository: BlogRepositoryImpl + + // DOC's + private val remoteDataSource = mockk(relaxed = true) + private val localDataSource = mockk(relaxed = true) + + @BeforeEach + fun setup() { + blogRepository = BlogRepositoryImpl( + remoteDataSource = remoteDataSource, + localDataSource = localDataSource, + coroutineScope = coroutineScope, + coroutineDispatcher = dispatcher + ) + } + + @ExperimentalTime + @Test + fun findingAllStories() = runBlockingTest { + every { remoteDataSource.findAll() } returns flowOf(Result.Success(PreviewData.Blog.data)) + coEvery { localDataSource.save(any()) } just Runs + + blogRepository.findAll().collect { + Assert.assertEquals(it.data, PreviewData.Blog.data) + } + + verify(exactly = 1) { remoteDataSource.findAll() } + coVerify(exactly = PreviewData.Blog.data.size) { + localDataSource.save(any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryTest.kt b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryTest.kt new file mode 100644 index 0000000..e308eba --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryTest.kt @@ -0,0 +1,100 @@ +package com.ericampire.android.androidstudycase.data.repository + +import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension +import com.ericampire.android.androidstudycase.common.CoroutineScopeExtension +import com.ericampire.android.androidstudycase.common.MainCoroutineExtension +import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LottieFileDataSource +import com.ericampire.android.androidstudycase.util.PreviewData +import com.ericampire.android.androidstudycase.util.Result +import com.ericampire.android.androidstudycase.util.data +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.time.ExperimentalTime + +@ExperimentalCoroutinesApi +@ExtendWith( + value = [ + MainCoroutineExtension::class, + CoroutineDispatcherExtension::class, + CoroutineScopeExtension::class + ] +) +class LottieFileRepositoryTest( + private val coroutineScope: TestCoroutineScope, + private val dispatcher: TestCoroutineDispatcher +) { + + // SUT + private lateinit var repository: LottieFileRepositoryImpl + + // DOC's + private val remoteDataSource = mockk(relaxed = true) + private val localDataSource = mockk(relaxed = true) + + @BeforeEach + fun setup() { + repository = LottieFileRepositoryImpl( + remoteDataSource = remoteDataSource, + localDataSource = localDataSource, + coroutineScope = coroutineScope, + coroutineDispatcher = dispatcher + ) + } + + @ExperimentalTime + @Test + fun findingFeaturedAnimation() = runBlockingTest { + every { remoteDataSource.findFeatured() } returns flowOf(Result.Success(PreviewData.Lottiefile.data)) + coEvery { localDataSource.save(any()) } just Runs + + repository.findFeatured().collect { + Assert.assertEquals(it.data, PreviewData.Lottiefile.data) + } + + verify(exactly = 1) { remoteDataSource.findFeatured() } + coVerify(exactly = PreviewData.Lottiefile.data.size) { + localDataSource.save(any()) + } + } + + @ExperimentalTime + @Test + fun findingRecentAnimation() = runBlockingTest { + every { remoteDataSource.findRecent() } returns flowOf(Result.Success(PreviewData.Lottiefile.data)) + coEvery { localDataSource.save(any()) } just Runs + + repository.findRecent().collect { + Assert.assertEquals(it.data, PreviewData.Lottiefile.data) + } + + verify(exactly = 1) { remoteDataSource.findRecent() } + coVerify(exactly = PreviewData.Lottiefile.data.size) { + localDataSource.save(any()) + } + } + + @ExperimentalTime + @Test + fun findingPopularAnimation() = runBlockingTest { + every { remoteDataSource.findPopular() } returns flowOf(Result.Success(PreviewData.Lottiefile.data)) + coEvery { localDataSource.save(any()) } just Runs + + repository.findPopular().collect { + Assert.assertEquals(it.data, PreviewData.Lottiefile.data) + } + + verify(exactly = 1) { remoteDataSource.findPopular() } + coVerify(exactly = PreviewData.Lottiefile.data.size) { + localDataSource.save(any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/UserRepositoryTest.kt b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/UserRepositoryTest.kt new file mode 100644 index 0000000..41b351f --- /dev/null +++ b/app/src/test/java/com/ericampire/android/androidstudycase/data/repository/UserRepositoryTest.kt @@ -0,0 +1,66 @@ +package com.ericampire.android.androidstudycase.data.repository + +import com.ericampire.android.androidstudycase.common.CoroutineDispatcherExtension +import com.ericampire.android.androidstudycase.common.CoroutineScopeExtension +import com.ericampire.android.androidstudycase.common.MainCoroutineExtension +import com.ericampire.android.androidstudycase.data.datasource.user.UserDataSource +import com.ericampire.android.androidstudycase.util.PreviewData +import com.ericampire.android.androidstudycase.util.Result +import com.ericampire.android.androidstudycase.util.data +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert +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, + CoroutineScopeExtension::class + ] +) +class UserRepositoryTest( + private val coroutineScope: TestCoroutineScope, + private val dispatcher: TestCoroutineDispatcher +) { + // SUT + private lateinit var repository: UserRepositoryImpl + + // DOC's + private val localDataSource = mockk(relaxed = true) + + @BeforeEach + fun setup() { + repository = UserRepositoryImpl(localDataSource) + } + + @Test + fun findCurrentUser() = runBlockingTest { + every { localDataSource.findAll() } returns flowOf(Result.Success(PreviewData.User.data)) + + repository.findAll().collect { + Assert.assertEquals(it.data, PreviewData.User.data) + } + + verify(exactly = 1) { localDataSource.findAll() } + } + + @Test + fun saveUser() = runBlockingTest { + coEvery { localDataSource.save(PreviewData.User.data.first()) } just Runs + + repository.save(PreviewData.User.data.first()) + + coVerify(exactly = 1) { + localDataSource.save(PreviewData.User.data.first()) + } + } +} \ No newline at end of file