ソースを参照

Merge pull request #3 from MrOzOn/refactoring_code

Refactoring code
MrOzOn 5 年 前
コミット
db79cbd240

+ 66 - 0
feature_auth/src/androidTest/java/com/mrozon/feature_auth/presentation/LoginFragmentTest.kt

@@ -0,0 +1,66 @@
+package com.mrozon.feature_auth.presentation
+
+import android.content.Context
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.mrozon.feature_auth.R
+import org.hamcrest.Matchers.not
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LoginFragmentTest {
+
+    lateinit var context: Context
+
+    @Before
+    fun setup() {
+        context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+    }
+
+    @Test
+    fun all_input_value_is_corrected() {
+
+        launchFragmentInContainer<LoginFragment>()
+
+        val password = "Password1!"
+        Espresso.onView(ViewMatchers.withId(R.id.etUserName)).perform(ViewActions.typeText("vasya@mail.ru"))
+        Espresso.onView(ViewMatchers.withId(R.id.etUserPassword)).perform(ViewActions.typeText(password))
+
+        Espresso.onView(ViewMatchers.withId(R.id.btnLogin))
+            .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
+    }
+
+    @Test
+    fun input_incorrect_email() {
+
+        launchFragmentInContainer<LoginFragment>()
+
+        val password = "Password1!"
+        Espresso.onView(ViewMatchers.withId(R.id.etUserName)).perform(ViewActions.typeText("vasyamail.ru"))
+        Espresso.onView(ViewMatchers.withId(R.id.etUserPassword)).perform(ViewActions.typeText(password))
+
+        Espresso.onView(ViewMatchers.withId(R.id.btnLogin))
+            .check(ViewAssertions.matches(not(ViewMatchers.isEnabled())))
+    }
+
+    @Test
+    fun input_empty_password() {
+
+        launchFragmentInContainer<LoginFragment>()
+
+        val password = ""
+        Espresso.onView(ViewMatchers.withId(R.id.etUserName)).perform(ViewActions.typeText("vasya@mail.ru"))
+        Espresso.onView(ViewMatchers.withId(R.id.etUserPassword)).perform(ViewActions.typeText(password))
+
+        Espresso.onView(ViewMatchers.withId(R.id.btnLogin))
+            .check(ViewAssertions.matches(not(ViewMatchers.isEnabled())))
+    }
+}

+ 6 - 4
feature_auth/src/main/java/com/mrozon/feature_auth/presentation/LoginFragmentViewModel.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
@@ -18,7 +19,8 @@ import kotlinx.coroutines.flow.combine
 import javax.inject.Inject
 
 class LoginFragmentViewModel @Inject constructor(
-    private val repository: UserAuthRepository
+    private val repository: UserAuthRepository,
+    private val coroutineContextProvider: CoroutineContextProvider
 ): BaseViewModel() {
 
     private val _loggedUser = MutableLiveData<Result<User>?>(null)
@@ -45,7 +47,7 @@ class LoginFragmentViewModel @Inject constructor(
                     }
                     .collect {
                         job?.cancel()
-                        job = async(Dispatchers.Main) {
+                        job = async(coroutineContextProvider.Main) {
                             value = validateInputData(it)
                         }
                     }
@@ -61,9 +63,9 @@ class LoginFragmentViewModel @Inject constructor(
     fun loginUser(){
         val userName = userNameChannel.value
         val psw = userPasswordChannel.value
-        viewModelScope.launch(Dispatchers.IO){
+        viewModelScope.launch(coroutineContextProvider.IO){
             repository.loginUser(userName,psw).collect {
-                withContext(Dispatchers.Main) {
+                withContext(coroutineContextProvider.Main) {
                     _loggedUser.value = it
                 }
             }

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

@@ -66,7 +66,7 @@ class RegistrationFragmentViewModel @Inject constructor(
                 }
                     .collect {
                         job?.cancel()
-                        job = async(Dispatchers.Main) {
+                        job = async(coroutineContextProvider.Main) {
                             value = if (!EMAIL_ADDRESS.matcher(it[0]).matches())
                                 ValidateDataError.INCORRECT_EMAIL
                             else if (it[1].isEmpty())

+ 135 - 0
feature_auth/src/test/java/com/mrozon/feature_auth/presentation/LoginFragmentViewModelTest.kt

@@ -0,0 +1,135 @@
+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.feature_auth.data.CoroutineTestRule
+import com.mrozon.feature_auth.data.UserAuthRepository
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.flowOf
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+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
+
+@ExperimentalCoroutinesApi
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest= Config.NONE)
+class LoginFragmentViewModelTest {
+
+    @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<Boolean>
+
+    lateinit var viewModel: LoginFragmentViewModel
+
+    @Before
+    fun setUp() {
+        viewModel = LoginFragmentViewModel(repository, TestCoroutineProvider())
+            .apply {
+                loggedUser.observeForever(observer)
+            }
+    }
+
+    @After
+    fun tearDown() {
+    }
+
+    @Test
+    fun `login user success`()  = coroutinesTestRule.runBlockingTest {
+        val email = "user@user.ru"
+        val password = "password"
+        viewModel.userNameChannel.offer(email)
+        viewModel.userPasswordChannel.offer(password)
+        val user = User(email = email, firstname = "firstName", lastname = "lastName")
+        Mockito.`when`(repository.loginUser(Mockito.anyString(), Mockito.anyString())).thenReturn(
+            flowOf(Result.loading(), Result.success(user))
+        )
+
+        viewModel.loginUser()
+
+        Mockito.verify(observer).onChanged(Result.loading())
+        Mockito.verify(observer).onChanged(Result.success(user))
+    }
+
+    @Test
+    fun `login user failed`()  = coroutinesTestRule.runBlockingTest {
+        val email = "user@user.ru"
+        val password = "password"
+        val error = "Bla-bla!"
+        viewModel.userNameChannel.offer(email)
+        viewModel.userPasswordChannel.offer(password)
+        Mockito.`when`(repository.loginUser(Mockito.anyString(), Mockito.anyString())).thenReturn(
+            flowOf(Result.loading(), Result.error(error))
+        )
+
+        viewModel.loginUser()
+
+        Mockito.verify(observer).onChanged(Result.loading())
+        Mockito.verify(observer).onChanged(Result.error(error))
+    }
+
+    @FlowPreview
+    @Test
+    fun `email typed is invalid`()  {
+
+        viewModel.enableLogin.observeForever(validateObserver)
+        viewModel.userNameChannel.offer("sdfgsds")
+        viewModel.userPasswordChannel.offer("1111")
+
+        Mockito.verify(validateObserver).onChanged(false)
+
+        viewModel.enableLogin.removeObserver(validateObserver)
+    }
+
+    @FlowPreview
+    @Test
+    fun `password is empty`()  {
+
+        viewModel.enableLogin.observeForever(validateObserver)
+        viewModel.userNameChannel.offer("sdfgs@ds.ru")
+        viewModel.userPasswordChannel.offer("")
+
+        Mockito.verify(validateObserver).onChanged(false)
+
+        viewModel.enableLogin.removeObserver(validateObserver)
+    }
+
+    @FlowPreview
+    @Test
+    fun `all inputs are correct`()  {
+
+        viewModel.enableLogin.observeForever(validateObserver)
+        viewModel.userNameChannel.offer("sdfgs@ds.ru")
+        viewModel.userPasswordChannel.offer("password")
+
+        Mockito.verify(validateObserver).onChanged(true)
+
+        viewModel.enableLogin.removeObserver(validateObserver)
+    }
+
+}

+ 28 - 10
feature_person/build.gradle

@@ -3,6 +3,8 @@ apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
 apply plugin: 'kotlin-kapt'
 
+apply from: "$project.rootDir/jacoco.gradle"
+
 android {
     compileSdkVersion rootProject.compileSdkVersion
     buildToolsVersion "29.0.3"
@@ -18,6 +20,9 @@ android {
     }
 
     buildTypes {
+        debug {
+            testCoverageEnabled true
+        }
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@@ -38,26 +43,39 @@ android {
 apply from: "$project.rootDir/scripts/deps_versions.gradle"
 
 dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation kotlinStdlib
+
     api project(':core_api')
     implementation project(':core')
+    implementation project(':utils')
+    //DAGGER
     implementation dagger
-    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
     kapt daggerCompiler
-    implementation project(':utils')
     implementation constraintlayout
+    //Timber
     implementation timber
     implementation navigationFragment
     implementation retrofit
+    //AndroidX
+    implementation legacySupport
+    implementation appCompat
+    implementation androidxCore
     implementation cardview
     implementation recyclerview
     implementation material
-
-    implementation fileTree(dir: "libs", include: ["*.jar"])
-    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
-    implementation 'androidx.core:core-ktx:1.3.1'
-    implementation 'androidx.appcompat:appcompat:1.2.0'
-    testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    //Unit test
+    testImplementation junit
+    testImplementation mockitoCore
+    testImplementation kotlinxCoroutinesTest
+    testImplementation robolectric
+    testImplementation androidXCoreTest
+    //Instrumental Test
+    androidTestImplementation junitInstrumental
+    androidTestImplementation androidXRulesTest
+    androidTestImplementation androidXRunnerTest
+    androidTestImplementation espressoCore
+    androidTestImplementation androidXFragmentTest
+    kaptAndroidTest daggerCompiler
 
 }