Forráskód Böngészése

refactoring deps_versions.gradle and build.gradle for feature_auth add robolectric
add unit test for RegistrationFragmentViewModel.kt

MrOzOn 5 éve
szülő
commit
3a5e24d9af

+ 10 - 0
core_api/src/main/java/com/mrozon/core_api/providers/CoroutineContextProvider.kt

@@ -0,0 +1,10 @@
+package com.mrozon.core_api.providers
+
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+open class CoroutineContextProvider @Inject constructor(){
+    open val Main: CoroutineContext by lazy { Dispatchers.Main }
+    open val IO: CoroutineContext by lazy { Dispatchers.IO }
+}

+ 2 - 0
feature_auth/build.gradle

@@ -60,6 +60,8 @@ dependencies {
     testImplementation junit
     testImplementation mockitoCore
     testImplementation kotlinxCoroutinesTest
+    testImplementation robolectric
+    testImplementation androidXCoreTest
     //Instrumental Test
     androidTestImplementation junitInstrumental
     androidTestImplementation espressoCore

+ 6 - 3
feature_auth/src/main/java/com/mrozon/feature_auth/presentation/RegistrationFragmentViewModel.kt

@@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.viewModelScope
 import com.mrozon.core_api.entity.User
+import com.mrozon.core_api.providers.CoroutineContextProvider
 import com.mrozon.feature_auth.data.UserAuthRepository
 import com.mrozon.feature_auth.data.UserAuthRepositoryImpl
 import com.mrozon.utils.base.BaseViewModel
@@ -16,9 +17,11 @@ import kotlinx.coroutines.flow.asFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 
 class RegistrationFragmentViewModel @Inject constructor(
-    private val repository: UserAuthRepository
+    private val repository: UserAuthRepository,
+    private val coroutineContextProvider: CoroutineContextProvider
 ): BaseViewModel() {
 
     private val _error = MutableLiveData<String?>()
@@ -88,9 +91,9 @@ class RegistrationFragmentViewModel @Inject constructor(
     fun registerUser() {
         val psw = passwordChannel.value
         val user = User(email = emailChannel.value, firstname = firstNameChannel.value, lastname = lastNameChannel.value)
-        viewModelScope.launch(Dispatchers.IO) {
+        viewModelScope.launch(coroutineContextProvider.IO) {
             repository.registerUser(user,psw).collect {
-                withContext(Dispatchers.Main) {
+                withContext(coroutineContextProvider.Main) {
                     _registeredUser.value = it
                 }
             }

+ 27 - 11
feature_auth/src/test/java/com/mrozon/feature_auth/data/UserAuthRepositoryImplTest.kt

@@ -7,10 +7,6 @@ import com.mrozon.utils.network.Result.Companion.loading
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.test.setMain
 import org.junit.Assert.*
 import org.junit.Rule
 import org.junit.Test
@@ -18,9 +14,11 @@ import org.junit.rules.TestWatcher
 import org.junit.runner.Description
 import com.mrozon.utils.network.Result.Companion.error
 import com.mrozon.utils.network.Result.Companion.success
+import kotlinx.coroutines.test.*
 import okhttp3.MediaType
 import okhttp3.ResponseBody
 import org.junit.Before
+import org.junit.runners.model.Statement
 import org.mockito.Mock
 import org.mockito.Mockito.*
 import org.mockito.junit.MockitoJUnit
@@ -175,14 +173,32 @@ class UserAuthRepositoryImplTest {
 
 @ExperimentalCoroutinesApi
 class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
-    override fun starting(description: Description?) {
-        super.starting(description)
-        Dispatchers.setMain(testDispatcher)
+
+    private val testCoroutinesScope = TestCoroutineScope(testDispatcher)
+
+//    override fun starting(description: Description?) {
+//        super.starting(description)
+//        Dispatchers.setMain(testDispatcher)
+//    }
+//
+//    override fun finished(description: Description?) {
+//        super.finished(description)
+//        Dispatchers.resetMain()
+//        testDispatcher.cleanupTestCoroutines()
+//    }
+
+    override fun apply(base: Statement, description: Description?) = object : Statement() {
+        override fun evaluate() {
+            Dispatchers.setMain(testDispatcher)
+            base.evaluate()
+            Dispatchers.resetMain()
+            testCoroutinesScope.cleanupTestCoroutines()
+        }
     }
 
-    override fun finished(description: Description?) {
-        super.finished(description)
-        Dispatchers.resetMain()
-        testDispatcher.cleanupTestCoroutines()
+    fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) {
+        testCoroutinesScope.runBlockingTest{
+            block()
+        }
     }
 }

+ 160 - 0
feature_auth/src/test/java/com/mrozon/feature_auth/presentation/RegistrationFragmentViewModelTest.kt

@@ -0,0 +1,160 @@
+package com.mrozon.feature_auth.presentation
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import com.mrozon.core_api.entity.User
+import com.mrozon.core_api.providers.CoroutineContextProvider
+import com.mrozon.feature_auth.data.CoroutineTestRule
+import com.mrozon.feature_auth.data.UserAuthRepository
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.flowOf
+import org.junit.*
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import kotlin.coroutines.CoroutineContext
+
+
+@ExperimentalCoroutinesApi
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest= Config.NONE)
+class RegistrationFragmentViewModelTest {
+
+    @get:Rule
+    val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    var coroutinesTestRule = CoroutineTestRule()
+
+    @get:Rule
+    val executorRule = InstantTaskExecutorRule()
+
+    @Mock
+    lateinit var repository: UserAuthRepository
+
+    @Mock
+    lateinit var observer: Observer<Result<User>?>
+
+    @Mock
+    lateinit var validateObserver: Observer<ValidateDataError>
+
+    lateinit var viewModel: RegistrationFragmentViewModel
+
+    @Before
+    fun setUp() {
+        viewModel = RegistrationFragmentViewModel(repository, TestCoroutineProvider())
+            .apply {
+                registeredUser.observeForever(observer)
+            }
+    }
+
+    @After
+    fun tearDown() {
+    }
+
+    @Test
+    fun `register user success`() = coroutinesTestRule.runBlockingTest {
+        val password = "password1"
+        val email = "vasya@mail.ru"
+        val firstName = "first"
+        val lastName = "last"
+        viewModel.passwordChannel.offer(password)
+        viewModel.emailChannel.offer(email)
+        viewModel.firstNameChannel.offer(firstName)
+        viewModel.lastNameChannel.offer(lastName)
+        val user = User(email = email, firstname = firstName, lastname = lastName)
+
+        Mockito.`when`(repository.registerUser(Mockito.any<User>()?:user, Mockito.anyString())).thenReturn(
+            flowOf(Result.loading(), Result.success(user))
+        )
+
+        viewModel.registerUser()
+
+        Mockito.verify(observer).onChanged(Result.loading())
+        Mockito.verify(observer).onChanged(Result.success(user))
+    }
+
+    @Test
+    fun `register user failed`() = coroutinesTestRule.runBlockingTest {
+        val password = "password1"
+        val email = "vasya@mail.ru"
+        val firstName = "first"
+        val lastName = "last"
+        val error = "Bla-bla!"
+        viewModel.passwordChannel.offer(password)
+        viewModel.emailChannel.offer(email)
+        viewModel.firstNameChannel.offer(firstName)
+        viewModel.lastNameChannel.offer(lastName)
+        val user = User(email = email, firstname = firstName, lastname = lastName)
+
+        Mockito.`when`(repository.registerUser(Mockito.any<User>()?:user, Mockito.anyString())).thenReturn(
+            flowOf(Result.loading(), Result.error(error))
+        )
+
+        viewModel.registerUser()
+
+        Mockito.verify(observer).onChanged(Result.loading())
+        Mockito.verify(observer).onChanged(Result.error(error))
+    }
+
+    @FlowPreview
+    @Test
+    fun `different password typed is failed`()  {
+
+        viewModel.validateData.observeForever(validateObserver)
+        viewModel.passwordChannel.offer("111")
+        viewModel.passwordConfirmChannel.offer("1111")
+        viewModel.emailChannel.offer("vasya@mail.ru")
+        viewModel.firstNameChannel.offer("firstName")
+        viewModel.lastNameChannel.offer("lastName")
+
+        Mockito.verify(validateObserver).onChanged(ValidateDataError.PASSWORD_NOT_EQUAL)
+
+        viewModel.validateData.removeObserver(validateObserver)
+    }
+
+    @FlowPreview
+    @Test
+    fun `input values all correct`() {
+
+        val password = "password"
+        viewModel.validateData.observeForever(validateObserver)
+        viewModel.passwordChannel.offer(password)
+        viewModel.passwordConfirmChannel.offer(password)
+        viewModel.emailChannel.offer("vasya@mail.ru")
+        viewModel.firstNameChannel.offer("firstName")
+        viewModel.lastNameChannel.offer("lastName")
+
+        Mockito.verify(validateObserver).onChanged(ValidateDataError.OK)
+
+        viewModel.validateData.removeObserver(validateObserver)
+    }
+
+    @FlowPreview
+    @Test
+    fun `incorrect email is failed`() {
+
+        val password = "password"
+        viewModel.validateData.observeForever(validateObserver)
+        viewModel.passwordChannel.offer(password)
+        viewModel.passwordConfirmChannel.offer(password)
+        viewModel.emailChannel.offer("vasyamail.ru")
+        viewModel.firstNameChannel.offer("firstName")
+        viewModel.lastNameChannel.offer("lastName")
+
+        Mockito.verify(validateObserver).onChanged(ValidateDataError.INCORRECT_EMAIL)
+
+        viewModel.validateData.removeObserver(validateObserver)
+    }
+
+}
+
+class TestCoroutineProvider: CoroutineContextProvider() {
+    override val Main: CoroutineContext = Dispatchers.Unconfined
+    override val IO: CoroutineContext = Dispatchers.Unconfined
+}

+ 4 - 0
scripts/deps_versions.gradle

@@ -21,6 +21,8 @@ ext {
     legacySupportVersion = "1.0.0"
     appCompatVersion = "1.2.0"
     androidxCoreVersion = "1.3.1"
+    robolectricVersion = "4.4"
+    androidXCoreTest = "2.1.0"
 
     // DI
     dagger = "com.google.dagger:dagger:$daggerVersion"
@@ -57,6 +59,8 @@ ext {
     kotlinxCoroutinesTest =  "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesTestVersion"
     junitInstrumental =  "androidx.test.ext:junit:$junitInstrumentalVersion"
     espressoCore = "androidx.test.espresso:espresso-core:$espressoCoreVersion"
+    robolectric = "org.robolectric:robolectric:$robolectricVersion"
+    androidXCoreTest = "androidx.arch.core:core-testing:$androidXCoreTest"
     // Support Library AndroidX
     legacySupport = "androidx.legacy:legacy-support-v4:$legacySupportVersion"
     appCompat =  "androidx.appcompat:appcompat:$appCompatVersion"