Преглед на файлове

add unit tests for LoginFragmentViewModel
add UI tests for LoginFragment

MrOzOn преди 5 години
родител
ревизия
cc6736b11e

+ 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())

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

@@ -0,0 +1,136 @@
+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)
+        val user = User(email = email, firstname = "firstName", lastname = "lastName")
+        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)
+    }
+
+}