From ce8c05b107b518af933eb5656e72ece6fa64480a Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 12:16:41 -0300 Subject: [PATCH 1/8] Adicionados testes de unidade para AuthViewModel e regra de dispatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Criado `MainDispatcherRule` para gerenciar coroutines em testes de unidade. - Adicionados testes para o `AuthViewModel`, cobrindo cenários de sucesso e falha de login, além de validações de e-mail e senha. - Removida validação de complexidade da senha, mantendo apenas a verificação de tamanho mínimo. - Incluídas dependências de `mockk` e `kotlinx-coroutines-test` para suportar os novos testes. --- app/build.gradle.kts | 6 + .../presentation/ui/login/AuthViewModel.kt | 8 +- .../java/com/delecrode/devhub/LoginTestes.kt | 154 ++++++++++++++++++ .../delecrode/devhub/MainDispatcherRule.kt | 24 +++ 4 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 app/src/test/java/com/delecrode/devhub/LoginTestes.kt create mode 100644 app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4655fc..cf231b5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,6 +50,12 @@ android { dependencies { + //Testes Unitarios e Instrumentados + testImplementation("io.mockk:mockk:1.13.10") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") + + //Room implementation(libs.room.runtime) implementation(libs.room.ktx) diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt index d67b16c..c523147 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt @@ -43,7 +43,8 @@ class AuthViewModel( when (val result = repository.signIn(email, password)) { is Result.Success -> { - _state.value = AuthState(isSuccess = true) + _state.value = AuthState(isSuccess = true, userUid = result.data.uid) + } is Result.Error -> { @@ -79,7 +80,6 @@ class AuthViewModel( return when { password.isBlank() -> "Senha é obrigatória" password.length < 6 -> "Senha deve ter pelo menos 6 caracteres" - !countPassword(password) -> "Senha deve conter pelo menos 1 letra e 1 número" else -> null } } @@ -87,8 +87,4 @@ class AuthViewModel( private fun isValidEmailFormat(email: String): Boolean { return Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email) } - - private fun countPassword(password: String): Boolean { - return password.length >= 6 - } } diff --git a/app/src/test/java/com/delecrode/devhub/LoginTestes.kt b/app/src/test/java/com/delecrode/devhub/LoginTestes.kt new file mode 100644 index 0000000..57f6fdc --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/LoginTestes.kt @@ -0,0 +1,154 @@ +package com.delecrode.devhub + +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserAuth +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.presentation.ui.login.AuthViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class LoginTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private lateinit var viewModel: AuthViewModel + + @Before + fun setup() { + viewModel = AuthViewModel(authRepository) + } + + //Testes de ViewModel + @Test + fun `quando signIn sucesso deve atualizar state com uid e sucesso`() = runTest { + val userAuth = UserAuth( + uid = "uid_123", + email = "teste@email.com" + ) + + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Success(userAuth) + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(state.isSuccess) + assert(state.userUid == "uid_123") + assert(!state.isLoading) + assert(state.error == null) + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email invalido na validacao local deve atualizar state com erro no email`() = + runTest { + + viewModel.signIn("teste", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email inválido") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email vazio na validacao local deve atualizar state com erro no email`() = runTest { + + viewModel.signIn("", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email é obrigatório") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha invalida na validacao local deve atualizar state com erro na senha`() = + runTest { + viewModel.signIn("teste@gmail.com", "teste") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha deve ter pelo menos 6 caracteres") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha vazia na validacao local deve atualizar state com erro na senha`() = runTest { + viewModel.signIn("teste@gmail.com", "") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha é obrigatória") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando signIn falhar deve atualizar state erro`() = runTest { + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Error("Credenciais invalidas") + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(state.userUid == null) + assert(!state.isLoading) + assert(state.error == "Credenciais invalidas") + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt b/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt new file mode 100644 index 0000000..6ce3475 --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@OptIn(ExperimentalCoroutinesApi::class) +class MainDispatcherRule( + private val dispatcher: TestDispatcher = StandardTestDispatcher() +) : TestWatcher() { + + override fun starting(description: Description) { + Dispatchers.setMain(dispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + } +} \ No newline at end of file From 227073a3fbc771829208b8bd0a69acefa89a0dc2 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 15:18:21 -0300 Subject: [PATCH 2/8] Refatorado testes e HomeViewModel - Movidos e reorganizados testes de `LoginTestes` para `auth/AuthTestes`. - Adicionado `user/UserTestes` para cobrir os casos de uso do `HomeViewModel`. - Refatorado `HomeViewModel` para encadear as chamadas de API do Firebase e GitHub, tratando os estados de sucesso e erro de forma mais robusta. - Removido arquivo de teste de exemplo `ExampleUnitTest.kt`. --- .../presentation/ui/home/HomeViewModel.kt | 49 +++- .../com/delecrode/devhub/ExampleUnitTest.kt | 17 -- .../java/com/delecrode/devhub/LoginTestes.kt | 154 ---------- .../com/delecrode/devhub/auth/AuthTestes.kt | 160 +++++++++++ .../com/delecrode/devhub/user/UserTestes.kt | 266 ++++++++++++++++++ 5 files changed, 460 insertions(+), 186 deletions(-) delete mode 100644 app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt delete mode 100644 app/src/test/java/com/delecrode/devhub/LoginTestes.kt create mode 100644 app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt create mode 100644 app/src/test/java/com/delecrode/devhub/user/UserTestes.kt diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt index 93ed70f..376814d 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt @@ -62,30 +62,49 @@ class HomeViewModel( fun getUserForFirebase() { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForFirebase()) { + _uiState.update { + it.copy(isLoading = true, error = null) + } + + when (val firebaseResult = userRepository.getUserForFirebase()) { is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForFirebase = result.data, - error = null - ) - getUserForGit(result.data.username) + val userFirebase = firebaseResult.data + + _uiState.update { + it.copy(userForFirebase = userFirebase, isLoading = false) + } + + when (val gitResult = userRepository.getUserForGitHub(userFirebase.username)) { + is Result.Success -> { + _uiState.update { + it.copy(userForGit = gitResult.data) + } + } + + is Result.Error -> { + _uiState.update { + it.copy( + error = gitResult.message, + isLoading = false + ) + } + } + } } is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) + _uiState.update { + it.copy( + error = firebaseResult.message, + isLoading = false + ) + } } } - } } + fun getUserForGit(userName: String) { viewModelScope.launch { _uiState.value = _uiState.value.copy( diff --git a/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt b/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt deleted file mode 100644 index 5d7d645..0000000 --- a/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.delecrode.devhub - -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/delecrode/devhub/LoginTestes.kt b/app/src/test/java/com/delecrode/devhub/LoginTestes.kt deleted file mode 100644 index 57f6fdc..0000000 --- a/app/src/test/java/com/delecrode/devhub/LoginTestes.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.delecrode.devhub - -import com.delecrode.devhub.data.utils.Result -import com.delecrode.devhub.domain.model.UserAuth -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.presentation.ui.login.AuthViewModel -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -class LoginTestes { - - @get:Rule - val dispatcherRule = MainDispatcherRule() - - private val authRepository: AuthRepository = mockk() - private lateinit var viewModel: AuthViewModel - - @Before - fun setup() { - viewModel = AuthViewModel(authRepository) - } - - //Testes de ViewModel - @Test - fun `quando signIn sucesso deve atualizar state com uid e sucesso`() = runTest { - val userAuth = UserAuth( - uid = "uid_123", - email = "teste@email.com" - ) - - coEvery { - authRepository.signIn(any(), any()) - } returns Result.Success(userAuth) - - viewModel.signIn("teste@email.com", "Teste123") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(state.isSuccess) - assert(state.userUid == "uid_123") - assert(!state.isLoading) - assert(state.error == null) - - coVerify(exactly = 1) { - authRepository.signIn(any(), any()) - } - } - - @Test - fun `quando email invalido na validacao local deve atualizar state com erro no email`() = - runTest { - - viewModel.signIn("teste", "Teste123") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(!state.isSuccess) - assert(!state.isLoading) - assert(state.userUid == null) - assert(state.emailError == "Email inválido") - - coVerify(exactly = 0) { - authRepository.signIn(any(), any()) - } - } - - @Test - fun `quando email vazio na validacao local deve atualizar state com erro no email`() = runTest { - - viewModel.signIn("", "Teste123") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(!state.isSuccess) - assert(!state.isLoading) - assert(state.userUid == null) - assert(state.emailError == "Email é obrigatório") - - coVerify(exactly = 0) { - authRepository.signIn(any(), any()) - } - } - - @Test - fun `quando senha invalida na validacao local deve atualizar state com erro na senha`() = - runTest { - viewModel.signIn("teste@gmail.com", "teste") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(!state.isSuccess) - assert(!state.isLoading) - assert(state.userUid == null) - assert(state.passwordError == "Senha deve ter pelo menos 6 caracteres") - - coVerify(exactly = 0) { - authRepository.signIn(any(), any()) - } - } - - @Test - fun `quando senha vazia na validacao local deve atualizar state com erro na senha`() = runTest { - viewModel.signIn("teste@gmail.com", "") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(!state.isSuccess) - assert(!state.isLoading) - assert(state.userUid == null) - assert(state.passwordError == "Senha é obrigatória") - - coVerify(exactly = 0) { - authRepository.signIn(any(), any()) - } - } - - @Test - fun `quando signIn falhar deve atualizar state erro`() = runTest { - coEvery { - authRepository.signIn(any(), any()) - } returns Result.Error("Credenciais invalidas") - - viewModel.signIn("teste@email.com", "Teste123") - - advanceUntilIdle() - - val state = viewModel.state.value - - assert(!state.isSuccess) - assert(state.userUid == null) - assert(!state.isLoading) - assert(state.error == "Credenciais invalidas") - - coVerify(exactly = 1) { - authRepository.signIn(any(), any()) - } - } -} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt b/app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt new file mode 100644 index 0000000..326062f --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt @@ -0,0 +1,160 @@ +package com.delecrode.devhub.auth + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserAuth +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.presentation.ui.login.AuthViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class AuthTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private lateinit var viewModel: AuthViewModel + + @Before + fun setup() { + viewModel = AuthViewModel(authRepository) + } + + + + + //Testes de ViewModel + @Test + fun `quando signIn sucesso deve atualizar state com uid e sucesso`() = runTest { + val userAuth = UserAuth( + uid = "uid_123", + email = "teste@email.com" + ) + + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Success(userAuth) + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(state.isSuccess) + assert(state.userUid == "uid_123") + assert(!state.isLoading) + assert(state.error == null) + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email invalido na validacao local deve atualizar state com erro no email`() = + runTest { + + viewModel.signIn("teste", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email inválido") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email vazio na validacao local deve atualizar state com erro no email`() = + runTest { + + viewModel.signIn("", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email é obrigatório") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha invalida na validacao local deve atualizar state com erro na senha`() = + runTest { + viewModel.signIn("teste@gmail.com", "teste") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha deve ter pelo menos 6 caracteres") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha vazia na validacao local deve atualizar state com erro na senha`() = + runTest { + viewModel.signIn("teste@gmail.com", "") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha é obrigatória") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando signIn falhar deve atualizar state erro`() = runTest { + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Error("Credenciais invalidas") + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(state.userUid == null) + assert(!state.isLoading) + assert(state.error == "Credenciais invalidas") + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt b/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt new file mode 100644 index 0000000..4ed5747 --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt @@ -0,0 +1,266 @@ +package com.delecrode.devhub.user + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.Repos +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.presentation.ui.home.HomeViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class UserTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private val userRepository: UserRepository = mockk() + private lateinit var homeViewModel: HomeViewModel + + @Before + fun setup() { + homeViewModel = HomeViewModel(userRepository, authRepository) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + val userForFirebase = UserForFirebase( + fullName = "teste full name", + username = "teste username", + email = "teste email" + ) + + val repos = listOf( + Repos( + id = 1, + node_id = "teste node id", + name = "teste name", + full_name = "teste full name", + private = false, + description = "teste description", + url = "teste url", + created_at = "teste created at", + updated_at = "teste updated at", + pushed_at = "teste pushed at", + clone_url = "teste clone url", + ) + ) + + //Testes de ViewModel + @Test + fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.getUserForGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } + + @Test + fun `quando buscar um usuario do GitHub falhar deve retornar erro`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.getUserForGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e retornar sucesso`() = + runTest { + coEvery { + userRepository.getUserForFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + userRepository.getUserForGitHub(userForFirebase.username) + } returns Result.Success(userForGit) + + homeViewModel.getUserForFirebase() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getUserForFirebase() + userRepository.getUserForGitHub(userForFirebase.username) + } + } + + @Test + fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { + coEvery { + userRepository.getUserForFirebase() + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.getUserForFirebase() + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForFirebase == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + userRepository.getUserForFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e falhar`() = + runTest { + coEvery { + userRepository.getUserForFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + userRepository.getUserForGitHub(userForFirebase.username) + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.getUserForFirebase() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + userRepository.getUserForFirebase() + userRepository.getUserForGitHub(userForFirebase.username) + } + } + + @Test + fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { + coEvery { + userRepository.getRepos("teste userName") + } returns Result.Success(repos) + + homeViewModel.getRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { + coEvery { + userRepository.getRepos("teste userName") + } returns Result.Error("Erro ao buscar repositorios") + + homeViewModel.getRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + userRepository.getRepos(any()) + } + } + + + @Test + fun `quando buscar um usuario atraves da pesquisa deve retornar sucesso`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.getUserForSearchGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } + + @Test + fun `quando buscar um usuario atraves da pesquisa falhar deve retornar erro`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.getUserForSearchGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } +} + From 5cbfd7abc1b88604c51af4b69d7bd56dedc162a4 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 16:05:47 -0300 Subject: [PATCH 3/8] =?UTF-8?q?Refatorado=20camada=20de=20dom=C3=ADnio=20e?= =?UTF-8?q?=20inje=C3=A7=C3=A3o=20de=20depend=C3=AAncia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduzido `UseCase` para encapsular a lógica de negócios, desacoplando `ViewModels` dos repositórios. - Criado `FetchUserDataUseCase` para buscar dados de usuário e repositórios. - Criado `AuthUseCase` para lidar com a lógica de autenticação (logout). - Reestruturada a injeção de dependência com Koin, separando o `AppModule` monolítico em módulos por camada: - `DataModule`: Para fontes de dados (API, DB, Firebase). - `RepositoryModule`: Para as implementações dos repositórios. - `UseCaseModule`: Para os novos casos de uso. - `ViewModelModule`: Para os ViewModels da aplicação. - Atualizado `HomeViewModel` e `ProfileViewModel` para utilizar os novos `UseCases`, simplificando sua implementação e responsabilidades. --- app/src/main/java/com/delecrode/devhub/App.kt | 10 +- .../java/com/delecrode/devhub/di/AppModule.kt | 74 -------- .../com/delecrode/devhub/di/DataModule.kt | 42 +++++ .../delecrode/devhub/di/RepositoryModule.kt | 24 +++ .../com/delecrode/devhub/di/UseCaseModule.kt | 11 ++ .../delecrode/devhub/di/ViewModelModule.kt | 24 +++ .../devhub/domain/useCase/AuthUseCase.kt | 11 ++ .../domain/useCase/fetchUserDataUseCase.kt | 17 ++ .../presentation/navigation/AppNavHost.kt | 2 +- .../devhub/presentation/ui/home/HomeScreen.kt | 3 +- .../presentation/ui/home/HomeViewModel.kt | 178 ++++++------------ .../presentation/ui/profile/ProfileScreen.kt | 2 +- .../ui/profile/ProfileViewModel.kt | 93 ++++----- 13 files changed, 234 insertions(+), 257 deletions(-) delete mode 100644 app/src/main/java/com/delecrode/devhub/di/AppModule.kt create mode 100644 app/src/main/java/com/delecrode/devhub/di/DataModule.kt create mode 100644 app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt create mode 100644 app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt create mode 100644 app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt create mode 100644 app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt create mode 100644 app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt diff --git a/app/src/main/java/com/delecrode/devhub/App.kt b/app/src/main/java/com/delecrode/devhub/App.kt index feabef8..b311e88 100644 --- a/app/src/main/java/com/delecrode/devhub/App.kt +++ b/app/src/main/java/com/delecrode/devhub/App.kt @@ -1,7 +1,10 @@ package com.delecrode.devhub import android.app.Application -import com.delecrode.devhub.di.appModule +import com.delecrode.devhub.di.dataModule +import com.delecrode.devhub.di.repositoryModule +import com.delecrode.devhub.di.useCaseModule +import com.delecrode.devhub.di.viewModelModule import org.koin.android.ext.koin.androidContext import org.koin.core.context.GlobalContext.startKoin @@ -10,7 +13,10 @@ class App : Application() { super.onCreate() startKoin { androidContext(this@App) - modules(appModule) + modules( dataModule, + repositoryModule, + useCaseModule, + viewModelModule) } } } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt b/app/src/main/java/com/delecrode/devhub/di/AppModule.kt deleted file mode 100644 index 62a871e..0000000 --- a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.delecrode.devhub.di - -import androidx.room.Room -import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource -import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSourceImpl -import com.delecrode.devhub.data.local.database.AppDatabase -import com.delecrode.devhub.data.local.database.MIGRATION_1_2 -import com.delecrode.devhub.data.local.database.data.RepoLocalDataSource -import com.delecrode.devhub.data.local.database.data.RepoLocalDataSourceImpl -import com.delecrode.devhub.data.remote.firebase.UserExtraData -import com.delecrode.devhub.data.remote.webApi.instance.RetrofitInstance -import com.delecrode.devhub.data.repository.AuthRepositoryImpl -import com.delecrode.devhub.data.repository.RepoRepositoryImpl -import com.delecrode.devhub.data.repository.UserRepositoryImpl -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.RepoRepository -import com.delecrode.devhub.domain.repository.UserRepository -import com.delecrode.devhub.domain.session.SessionViewModel -import com.delecrode.devhub.presentation.ui.favoritos.RepoFavViewModel -import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel -import com.delecrode.devhub.presentation.ui.home.HomeViewModel -import com.delecrode.devhub.presentation.ui.login.AuthViewModel -import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel -import com.delecrode.devhub.presentation.ui.register.RegisterViewModel -import com.delecrode.devhub.presentation.ui.repo.RepoDetailViewModel -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.firestore.FirebaseFirestore -import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module - - -val appModule = module { - - single { FirebaseAuth.getInstance() } - single { FirebaseFirestore.getInstance() } - - single { RetrofitInstance.userApi } - single { RetrofitInstance.repoApi } - - single { - Room.databaseBuilder( - androidContext(), - AppDatabase::class.java, - "app_database" - ) - .addMigrations(MIGRATION_1_2) - .build() - - } - - single { - get().repoDao() - } - - single { AuthLocalDataSourceImpl(get()) } - single { RepoLocalDataSourceImpl(get()) } - - single { com.delecrode.devhub.data.remote.firebase.FirebaseAuth(get()) } - single { UserExtraData(get()) } - - single { UserRepositoryImpl(get(), get(), get()) } - single { RepoRepositoryImpl(get(), get(), get()) } - single { AuthRepositoryImpl(get(), get(), get()) } - - viewModel { HomeViewModel(get(), get()) } - viewModel { RepoDetailViewModel(get()) } - viewModel { AuthViewModel(get()) } - viewModel { SessionViewModel(get()) } - viewModel { RegisterViewModel(get(),get()) } - viewModel { ProfileViewModel(get(), get()) } - viewModel { RepoFavViewModel(get()) } - viewModel { ForgotPasswordViewModel(get()) } -} \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/di/DataModule.kt b/app/src/main/java/com/delecrode/devhub/di/DataModule.kt new file mode 100644 index 0000000..c38e56c --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/DataModule.kt @@ -0,0 +1,42 @@ +package com.delecrode.devhub.di + +import androidx.room.Room +import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource +import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSourceImpl +import com.delecrode.devhub.data.local.database.AppDatabase +import com.delecrode.devhub.data.local.database.MIGRATION_1_2 +import com.delecrode.devhub.data.local.database.data.RepoLocalDataSource +import com.delecrode.devhub.data.local.database.data.RepoLocalDataSourceImpl +import com.delecrode.devhub.data.remote.firebase.UserExtraData +import com.delecrode.devhub.data.remote.webApi.instance.RetrofitInstance +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val dataModule = module { + + single { FirebaseAuth.getInstance() } + single { FirebaseFirestore.getInstance() } + + single { RetrofitInstance.userApi } + single { RetrofitInstance.repoApi } + + single { + Room.databaseBuilder( + androidContext(), + AppDatabase::class.java, + "app_database" + ) + .addMigrations(MIGRATION_1_2) + .build() + } + + single { get().repoDao() } + + single { AuthLocalDataSourceImpl(get()) } + single { RepoLocalDataSourceImpl(get()) } + + single { com.delecrode.devhub.data.remote.firebase.FirebaseAuth(get()) } + single { UserExtraData(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt b/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt new file mode 100644 index 0000000..5430e9e --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub.di + +import com.delecrode.devhub.data.repository.AuthRepositoryImpl +import com.delecrode.devhub.data.repository.RepoRepositoryImpl +import com.delecrode.devhub.data.repository.UserRepositoryImpl +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.RepoRepository +import com.delecrode.devhub.domain.repository.UserRepository +import org.koin.dsl.module + +val repositoryModule = module { + + single { + UserRepositoryImpl(get(), get(), get()) + } + + single { + RepoRepositoryImpl(get(), get(), get()) + } + + single { + AuthRepositoryImpl(get(), get(), get()) + } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt b/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt new file mode 100644 index 0000000..606e5ea --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt @@ -0,0 +1,11 @@ +package com.delecrode.devhub.di + +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import org.koin.dsl.module + +val useCaseModule = module { + + factory { FetchUserDataUseCase(get()) } + factory { AuthUseCase(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt b/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt new file mode 100644 index 0000000..58032bd --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub.di + +import HomeViewModel +import com.delecrode.devhub.domain.session.SessionViewModel +import com.delecrode.devhub.presentation.ui.favoritos.RepoFavViewModel +import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel +import com.delecrode.devhub.presentation.ui.login.AuthViewModel +import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel +import com.delecrode.devhub.presentation.ui.register.RegisterViewModel +import com.delecrode.devhub.presentation.ui.repo.RepoDetailViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val viewModelModule = module { + + viewModel { HomeViewModel(get(), get()) } + viewModel { ProfileViewModel(get(), get()) } + viewModel { AuthViewModel(get()) } + viewModel { SessionViewModel(get()) } + viewModel { RegisterViewModel(get(), get()) } + viewModel { RepoDetailViewModel(get()) } + viewModel { RepoFavViewModel(get()) } + viewModel { ForgotPasswordViewModel(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt b/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt new file mode 100644 index 0000000..36a8344 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt @@ -0,0 +1,11 @@ +package com.delecrode.devhub.domain.useCase + +import com.delecrode.devhub.domain.repository.AuthRepository + +class AuthUseCase ( + private val authRepository: AuthRepository + +){ + suspend fun signOut() = + authRepository.signOut() +} \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt new file mode 100644 index 0000000..74e94a2 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt @@ -0,0 +1,17 @@ +package com.delecrode.devhub.domain.useCase + +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.UserRepository + +class FetchUserDataUseCase( + private val userRepository: UserRepository, +) { + suspend fun loadUserFromFirebase() = + userRepository.getUserForFirebase() + + suspend fun loadUserFromGit(username: String) = + userRepository.getUserForGitHub(username) + + suspend fun loadRepos(username: String) = + userRepository.getRepos(username) +} diff --git a/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt b/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt index a5759cd..11bd0a7 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.presentation.navigation +import HomeViewModel import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.navigation.NavType @@ -13,7 +14,6 @@ import com.delecrode.devhub.presentation.ui.favoritos.ReposFavScreen import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordScreen import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel import com.delecrode.devhub.presentation.ui.home.HomeScreen -import com.delecrode.devhub.presentation.ui.home.HomeViewModel import com.delecrode.devhub.presentation.ui.login.AuthViewModel import com.delecrode.devhub.presentation.ui.login.LoginScreen import com.delecrode.devhub.presentation.ui.profile.ProfileScreen diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt index c7ca8d5..8e3e933 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.presentation.ui.home +import HomeViewModel import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -89,7 +90,7 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { } } LaunchedEffect(Unit) { - homeViewModel.getUserForFirebase() + homeViewModel.loadHome() } Scaffold( diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt index 376814d..10ed45b 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt @@ -1,183 +1,106 @@ -package com.delecrode.devhub.presentation.ui.home - -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import com.delecrode.devhub.presentation.ui.home.HomeState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch + class HomeViewModel( - private val userRepository: UserRepository, - private val authRepository: AuthRepository -) : - ViewModel() { + private val fetchUserData: FetchUserDataUseCase, + private val authUseCase: AuthUseCase +) : ViewModel() { private val _uiState = MutableStateFlow(HomeState()) val uiState: StateFlow = _uiState fun onSearchTextChange(value: String) { - _uiState.update { - it.copy(searchText = value) - } + _uiState.update { it.copy(searchText = value) } } fun onSearchClick() { val search = uiState.value.searchText if (search.isBlank()) return - getRepos(search) - getUserForSearchGit(search) + loadSearchUser(search) + loadRepos(search) } - - fun getUserForSearchGit(userName: String) { + private fun loadSearchUser(username: String) { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForSearchGit = result.data, - isLoading = false, - error = null - ) - } + _uiState.update { it.copy(isLoading = true, error = null) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } + when (val result = fetchUserData.loadUserFromGit(username)) { + is Result.Success -> + _uiState.update { + it.copy(userForSearchGit = result.data, isLoading = false) + } + + is Result.Error -> + _uiState.update { + it.copy(error = result.message, isLoading = false) + } } } } - fun getUserForFirebase() { + fun loadHome() { viewModelScope.launch { - _uiState.update { - it.copy(isLoading = true, error = null) - } + _uiState.update { it.copy(isLoading = true, error = null) } - when (val firebaseResult = userRepository.getUserForFirebase()) { + when (val firebase = fetchUserData.loadUserFromFirebase()) { is Result.Success -> { - val userFirebase = firebaseResult.data - - _uiState.update { - it.copy(userForFirebase = userFirebase, isLoading = false) - } + _uiState.update { it.copy(userForFirebase = firebase.data) } - when (val gitResult = userRepository.getUserForGitHub(userFirebase.username)) { - is Result.Success -> { + when (val git = fetchUserData.loadUserFromGit(firebase.data.username)) { + is Result.Success -> _uiState.update { - it.copy(userForGit = gitResult.data) + it.copy(userForGit = git.data, isLoading = false) } - } - is Result.Error -> { + is Result.Error -> _uiState.update { - it.copy( - error = gitResult.message, - isLoading = false - ) + it.copy(error = git.message, isLoading = false) } - } } } - is Result.Error -> { + is Result.Error -> _uiState.update { - it.copy( - error = firebaseResult.message, - isLoading = false - ) + it.copy(error = firebase.message, isLoading = false) } - } } } } - - fun getUserForGit(userName: String) { + private fun loadRepos(username: String) { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForGit = result.data, - isLoading = false, - error = null - ) - } + when (val result = fetchUserData.loadRepos(username)) { + is Result.Success -> + _uiState.update { it.copy(repos = result.data) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } + is Result.Error -> + _uiState.update { it.copy(error = result.message) } } } } - - - fun getRepos(userName: String) { + fun signOut() { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getRepos(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - repos = result.data, - isLoading = false, - error = null - ) - } - - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) + try { + authUseCase.signOut() + } catch (e: Exception) { + _uiState.update { + it.copy(error = e.message, isLoading = false) } } } } - fun signOut() { - viewModelScope.launch { - try { - authRepository.signOut() - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao fazer logout", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) - } - } - } - - fun clearStates() { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = null - ) - } - fun clearUi() { _uiState.update { it.copy( @@ -188,5 +111,14 @@ class HomeViewModel( ) } } -} + fun clearStates() { + _uiState.update { + it.copy( + userForFirebase = null, + userForGit = null, + userForSearchGit = null + ) + } + } +} diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt index 5501df3..eee8d89 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt @@ -59,7 +59,7 @@ fun ProfileScreen(navController: NavController, viewModel: ProfileViewModel) { val repos = state.value.repos LaunchedEffect(Unit) { - viewModel.getUserForFirebase() + viewModel.loadProfile() } LaunchedEffect(state.value.error) { diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt index cabfb0d..8acf9a1 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt @@ -6,81 +6,66 @@ import androidx.lifecycle.viewModelScope import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class ProfileViewModel( - private val userRepository: UserRepository, - private val authRepository: AuthRepository + private val fetchUserData: FetchUserDataUseCase, + private val authUseCase: AuthUseCase ) : ViewModel() { private val _uiState = MutableStateFlow(ProfileState()) val uiState: StateFlow = _uiState - fun getUserForFirebase() { - _uiState.value = _uiState.value.copy( - isLoading = true - ) + fun loadProfile() { viewModelScope.launch { - when (val result = userRepository.getUserForFirebase()) { + _uiState.update { it.copy(isLoading = true, error = null) } + + when (val firebase = fetchUserData.loadUserFromFirebase()) { is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForFirebase = result.data, - isLoading = false - ) - getUserForGit(result.data.username) - } + val firebaseUser = firebase.data + _uiState.update { it.copy(userForFirebase = firebaseUser) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) - } - } - } - } + when (val git = fetchUserData.loadUserFromGit(firebaseUser.username)) { + is Result.Success -> { + val gitUser = git.data + _uiState.update { it.copy(userForGit = gitUser) } - fun getUserForGit(userName: String) { - viewModelScope.launch { - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForGit = result.data, - isLoading = false - ) - getRepos(userName) + loadRepos(firebaseUser.username) + + _uiState.update { it.copy(isLoading = false) } + } + + is Result.Error -> { + _uiState.update { + it.copy(error = git.message, isLoading = false) + } + } + } } is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) + _uiState.update { it.copy(error = firebase.message, isLoading = false) } } } } } - fun getRepos(userName: String) { + + private fun loadRepos(username: String) { viewModelScope.launch { - when (val result = userRepository.getRepos(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - repos = result.data, - isLoading = false - ) - } + when (val result = fetchUserData.loadRepos(username)) { + is Result.Success -> + _uiState.update { it.copy(repos = result.data) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) - } + is Result.Error -> + _uiState.update { it.copy(error = result.message) } } } } @@ -88,13 +73,11 @@ class ProfileViewModel( fun signOut() { viewModelScope.launch { try { - authRepository.signOut() + authUseCase.signOut() } catch (e: Exception) { - Log.e("ProfileViewModel", "Erro ao fazer logout", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + _uiState.update { + it.copy(error = e.message, isLoading = false) + } } } } From 19db1308a5d8cdabd0c42ac4cc2a7c6a62a86e29 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 16:13:43 -0300 Subject: [PATCH 4/8] =?UTF-8?q?Refatora=C3=A7=C3=A3o:=20Movidos=20testes?= =?UTF-8?q?=20de=20ViewModel=20e=20removidos=20imports=20n=C3=A3o=20utiliz?= =?UTF-8?q?ados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Excluído arquivo de teste `UserTestes.kt` que testava o `HomeViewModel`. - Movido `AuthTestes.kt` para o novo pacote `viewModel/auth` para melhor organização. - Removidos imports não utilizados nos arquivos `ProfileViewModel` e `FetchUserDataUseCase`. --- .../domain/useCase/fetchUserDataUseCase.kt | 1 - .../ui/profile/ProfileViewModel.kt | 3 - .../com/delecrode/devhub/user/UserTestes.kt | 266 ------------------ .../devhub/{ => viewModel}/auth/AuthTestes.kt | 7 +- 4 files changed, 1 insertion(+), 276 deletions(-) delete mode 100644 app/src/test/java/com/delecrode/devhub/user/UserTestes.kt rename app/src/test/java/com/delecrode/devhub/{ => viewModel}/auth/AuthTestes.kt (98%) diff --git a/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt index 74e94a2..5446768 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt @@ -1,6 +1,5 @@ package com.delecrode.devhub.domain.useCase -import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.domain.repository.UserRepository class FetchUserDataUseCase( diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt index 8acf9a1..f0f3042 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt @@ -1,10 +1,7 @@ package com.delecrode.devhub.presentation.ui.profile -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.data.utils.Result import com.delecrode.devhub.domain.useCase.AuthUseCase import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase diff --git a/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt b/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt deleted file mode 100644 index 4ed5747..0000000 --- a/app/src/test/java/com/delecrode/devhub/user/UserTestes.kt +++ /dev/null @@ -1,266 +0,0 @@ -package com.delecrode.devhub.user - -import com.delecrode.devhub.MainDispatcherRule -import com.delecrode.devhub.data.utils.Result -import com.delecrode.devhub.domain.model.Repos -import com.delecrode.devhub.domain.model.UserForFirebase -import com.delecrode.devhub.domain.model.UserForGit -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.UserRepository -import com.delecrode.devhub.presentation.ui.home.HomeViewModel -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -class UserTestes { - - @get:Rule - val dispatcherRule = MainDispatcherRule() - - private val authRepository: AuthRepository = mockk() - private val userRepository: UserRepository = mockk() - private lateinit var homeViewModel: HomeViewModel - - @Before - fun setup() { - homeViewModel = HomeViewModel(userRepository, authRepository) - } - - val userForGit = UserForGit( - login = "teste userName", - avatar_url = "teste avatar", - url = "teste url", - name = "teste nome", - bio = "teste bio", - repos_url = "teste repos url" - ) - - val userForFirebase = UserForFirebase( - fullName = "teste full name", - username = "teste username", - email = "teste email" - ) - - val repos = listOf( - Repos( - id = 1, - node_id = "teste node id", - name = "teste name", - full_name = "teste full name", - private = false, - description = "teste description", - url = "teste url", - created_at = "teste created at", - updated_at = "teste updated at", - pushed_at = "teste pushed at", - clone_url = "teste clone url", - ) - ) - - //Testes de ViewModel - @Test - fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { - coEvery { - userRepository.getUserForGitHub("teste userName") - } returns Result.Success(userForGit) - - homeViewModel.getUserForGit("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.userForGit == userForGit) - assert(state.error == null) - - coVerify(exactly = 1) { - userRepository.getUserForGitHub(any()) - } - } - - @Test - fun `quando buscar um usuario do GitHub falhar deve retornar erro`() = runTest { - coEvery { - userRepository.getUserForGitHub("teste userName") - } returns Result.Error("Erro ao buscar usuario") - - homeViewModel.getUserForGit("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.userForGit == null) - assert(state.error == "Erro ao buscar usuario") - - coVerify(exactly = 1) { - userRepository.getUserForGitHub(any()) - } - } - - @Test - fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e retornar sucesso`() = - runTest { - coEvery { - userRepository.getUserForFirebase() - } returns Result.Success(userForFirebase) - - coEvery { - userRepository.getUserForGitHub(userForFirebase.username) - } returns Result.Success(userForGit) - - homeViewModel.getUserForFirebase() - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(state.userForFirebase == userForFirebase) - assert(state.userForGit == userForGit) - assert(state.error == null) - - coVerify(exactly = 1) { - userRepository.getUserForFirebase() - userRepository.getUserForGitHub(userForFirebase.username) - } - } - - @Test - fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { - coEvery { - userRepository.getUserForFirebase() - } returns Result.Error("Erro ao buscar usuario") - - homeViewModel.getUserForFirebase() - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.userForFirebase == null) - assert(state.error == "Erro ao buscar usuario") - - coVerify(exactly = 1) { - userRepository.getUserForFirebase() - } - } - - @Test - fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e falhar`() = - runTest { - coEvery { - userRepository.getUserForFirebase() - } returns Result.Success(userForFirebase) - - coEvery { - userRepository.getUserForGitHub(userForFirebase.username) - } returns Result.Error("Erro ao buscar usuario") - - homeViewModel.getUserForFirebase() - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(state.userForFirebase == userForFirebase) - assert(state.userForGit == null) - assert(state.error == "Erro ao buscar usuario") - - coVerify(exactly = 1) { - userRepository.getUserForFirebase() - userRepository.getUserForGitHub(userForFirebase.username) - } - } - - @Test - fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { - coEvery { - userRepository.getRepos("teste userName") - } returns Result.Success(repos) - - homeViewModel.getRepos("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.repos == repos) - assert(state.error == null) - - coVerify(exactly = 1) { - userRepository.getRepos(any()) - } - } - - @Test - fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { - coEvery { - userRepository.getRepos("teste userName") - } returns Result.Error("Erro ao buscar repositorios") - - homeViewModel.getRepos("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.repos.isEmpty()) - assert(state.error == "Erro ao buscar repositorios") - - coVerify(exactly = 1) { - userRepository.getRepos(any()) - } - } - - - @Test - fun `quando buscar um usuario atraves da pesquisa deve retornar sucesso`() = runTest { - coEvery { - userRepository.getUserForGitHub("teste userName") - } returns Result.Success(userForGit) - - homeViewModel.getUserForSearchGit("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.userForSearchGit == userForGit) - assert(state.error == null) - - coVerify(exactly = 1) { - userRepository.getUserForGitHub(any()) - } - } - - @Test - fun `quando buscar um usuario atraves da pesquisa falhar deve retornar erro`() = runTest { - coEvery { - userRepository.getUserForGitHub("teste userName") - } returns Result.Error("Erro ao buscar usuario") - - homeViewModel.getUserForSearchGit("teste userName") - - advanceUntilIdle() - - val state = homeViewModel.uiState.value - - assert(!state.isLoading) - assert(state.userForSearchGit == null) - assert(state.error == "Erro ao buscar usuario") - - coVerify(exactly = 1) { - userRepository.getUserForGitHub(any()) - } - } -} - diff --git a/app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt similarity index 98% rename from app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt rename to app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt index 326062f..ca46010 100644 --- a/app/src/test/java/com/delecrode/devhub/auth/AuthTestes.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt @@ -1,4 +1,4 @@ -package com.delecrode.devhub.auth +package com.delecrode.devhub.viewModel.auth import com.delecrode.devhub.MainDispatcherRule import com.delecrode.devhub.data.utils.Result @@ -26,11 +26,6 @@ class AuthTestes { fun setup() { viewModel = AuthViewModel(authRepository) } - - - - - //Testes de ViewModel @Test fun `quando signIn sucesso deve atualizar state com uid e sucesso`() = runTest { val userAuth = UserAuth( From f40274eb47efb4480b9f3eb66f216424a9f79667 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 16:41:38 -0300 Subject: [PATCH 5/8] Refatorado HomeViewModel e adicionado testes de unidade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Desmembrada a função `loadHome` em `loadUserFromFirebase` e `loadUserFromGit` para separar as responsabilidades e facilitar os testes. - A visibilidade das funções `loadSearchUser` e `loadRepos` foi alterada de `private` para `fun`. - Adicionados testes de unidade para `HomeViewModel` para cobrir os cenários de sucesso e erro ao buscar usuários (Git e Firebase) e repositórios. --- .../presentation/ui/home/HomeViewModel.kt | 51 ++-- .../devhub/viewModel/home/HomeTests.kt | 261 ++++++++++++++++++ 2 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt index 10ed45b..699a896 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt @@ -1,6 +1,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit import com.delecrode.devhub.domain.useCase.AuthUseCase import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase import com.delecrode.devhub.presentation.ui.home.HomeState @@ -30,7 +32,7 @@ class HomeViewModel( loadRepos(search) } - private fun loadSearchUser(username: String) { + fun loadSearchUser(username: String) { viewModelScope.launch { _uiState.update { it.copy(isLoading = true, error = null) } @@ -50,34 +52,39 @@ class HomeViewModel( fun loadHome() { viewModelScope.launch { - _uiState.update { it.copy(isLoading = true, error = null) } + val firebaseResult = loadUserFromFirebase() + if (firebaseResult is Result.Success) { + loadUserFromGit(firebaseResult.data.username) + } + } + } - when (val firebase = fetchUserData.loadUserFromFirebase()) { - is Result.Success -> { - _uiState.update { it.copy(userForFirebase = firebase.data) } + suspend fun loadUserFromFirebase(): Result { + _uiState.update { it.copy(isLoading = true, error = null) } - when (val git = fetchUserData.loadUserFromGit(firebase.data.username)) { - is Result.Success -> - _uiState.update { - it.copy(userForGit = git.data, isLoading = false) - } + val result = fetchUserData.loadUserFromFirebase() + when (result) { + is Result.Success -> _uiState.update { it.copy(userForFirebase = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } + } + return result + } - is Result.Error -> - _uiState.update { - it.copy(error = git.message, isLoading = false) - } - } - } + suspend fun loadUserFromGit(username: String): Result { + _uiState.update { it.copy(isLoading = true, error = null) } - is Result.Error -> - _uiState.update { - it.copy(error = firebase.message, isLoading = false) - } - } + val result = fetchUserData.loadUserFromGit(username) + when (result) { + is Result.Success -> _uiState.update { it.copy(userForGit = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } } + return result } - private fun loadRepos(username: String) { + + + + fun loadRepos(username: String) { viewModelScope.launch { when (val result = fetchUserData.loadRepos(username)) { is Result.Success -> diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt b/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt new file mode 100644 index 0000000..31e90fe --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt @@ -0,0 +1,261 @@ +package com.delecrode.devhub.viewModel.home + +import HomeViewModel +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.Repos +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class HomeTests { + + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + val fetchUserData: FetchUserDataUseCase = mockk() + val authUseCase: AuthUseCase = mockk() + + private lateinit var homeViewModel: HomeViewModel + + @Before + fun setup() { + homeViewModel = HomeViewModel(fetchUserData, authUseCase) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + val userForFirebase = UserForFirebase( + fullName = "teste full name", + username = "teste username", + email = "teste email" + ) + + val repos = listOf( + Repos( + id = 1, + node_id = "teste node id", + name = "teste name", + full_name = "teste full name", + private = false, + description = "teste description", + url = "teste url", + created_at = "teste created at", + updated_at = "teste updated at", + pushed_at = "teste pushed at", + clone_url = "teste clone url", + ) + ) + + @Test + fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + + @Test + fun `quando buscar um usuario do GitHub falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadSearchUser("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + homeViewModel.loadUserFromFirebase() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadHome() + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForFirebase == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e falhar`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadHome() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + } + } + + @Test + fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Success(repos) + + homeViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Error("Erro ao buscar repositorios") + + homeViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + + @Test + fun `quando buscar um usuario atraves da pesquisa deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.loadSearchUser("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario atraves da pesquisa falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } +} \ No newline at end of file From ebb5d75ff3bed1fdb358faaf2fbaf0a822cbccaf Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 17:15:58 -0300 Subject: [PATCH 6/8] =?UTF-8?q?Adicionados=20testes=20unit=C3=A1rios=20e?= =?UTF-8?q?=20refatorada=20a=20ProfileViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Criado o arquivo `ProfileTestes.kt` para adicionar testes de unidade abrangentes para a `ProfileViewModel`. - Refatorada a `ProfileViewModel` para separar a lógica de busca de dados do Firebase, Git e repositórios em funções `suspend` independentes, facilitando os testes. - Ajustado o fluxo de carregamento do perfil para usar as novas funções refatoradas. - Renomeado `HomeTests.kt` para `HomeTestes.kt` para seguir o padrão de nomenclatura. --- .../ui/profile/ProfileViewModel.kt | 65 ++-- .../home/{HomeTests.kt => HomeTestes.kt} | 4 +- .../devhub/viewModel/profile/ProfileTestes.kt | 283 ++++++++++++++++++ 3 files changed, 315 insertions(+), 37 deletions(-) rename app/src/test/java/com/delecrode/devhub/viewModel/home/{HomeTests.kt => HomeTestes.kt} (99%) create mode 100644 app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt index f0f3042..6c134e4 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt @@ -3,6 +3,8 @@ package com.delecrode.devhub.presentation.ui.profile import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit import com.delecrode.devhub.domain.useCase.AuthUseCase import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase import kotlinx.coroutines.flow.MutableStateFlow @@ -23,47 +25,42 @@ class ProfileViewModel( fun loadProfile() { viewModelScope.launch { _uiState.update { it.copy(isLoading = true, error = null) } - - when (val firebase = fetchUserData.loadUserFromFirebase()) { - is Result.Success -> { - val firebaseUser = firebase.data - _uiState.update { it.copy(userForFirebase = firebaseUser) } - - when (val git = fetchUserData.loadUserFromGit(firebaseUser.username)) { - is Result.Success -> { - val gitUser = git.data - _uiState.update { it.copy(userForGit = gitUser) } - - loadRepos(firebaseUser.username) - - _uiState.update { it.copy(isLoading = false) } - } - - is Result.Error -> { - _uiState.update { - it.copy(error = git.message, isLoading = false) - } - } - } - } - - is Result.Error -> { - _uiState.update { it.copy(error = firebase.message, isLoading = false) } + val firebaseResult = loadUserFromFirebase() + if (firebaseResult is Result.Success) { + val gitResult = loadUserFromGit(firebaseResult.data.username) + if (gitResult is Result.Success && gitResult.data.login != null) { + loadRepos(gitResult.data.login) } } } } + suspend fun loadUserFromFirebase(): Result { + val result = fetchUserData.loadUserFromFirebase() + when (result) { + is Result.Success -> _uiState.update { it.copy(userForFirebase = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } + } + return result + } - private fun loadRepos(username: String) { - viewModelScope.launch { - when (val result = fetchUserData.loadRepos(username)) { - is Result.Success -> - _uiState.update { it.copy(repos = result.data) } + suspend fun loadUserFromGit(username: String): Result { + val result = fetchUserData.loadUserFromGit(username) + when (result) { + is Result.Success -> _uiState.update { it.copy(userForGit = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } + } + return result + } - is Result.Error -> - _uiState.update { it.copy(error = result.message) } - } + + suspend fun loadRepos(username: String) { + when (val result = fetchUserData.loadRepos(username)) { + is Result.Success -> + _uiState.update { it.copy(repos = result.data, isLoading = false) } + + is Result.Error -> + _uiState.update { it.copy(error = result.message, isLoading = false) } } } diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt b/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt similarity index 99% rename from app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt rename to app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt index 31e90fe..5f1bb48 100644 --- a/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTests.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt @@ -17,9 +17,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test -class HomeTests { - - +class HomeTestes { @get:Rule val dispatcherRule = MainDispatcherRule() diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt new file mode 100644 index 0000000..642573b --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt @@ -0,0 +1,283 @@ +package com.delecrode.devhub.viewModel.profile + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.Repos +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class ProfileTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + val fetchUserData: FetchUserDataUseCase = mockk() + val authUseCase: AuthUseCase = mockk() + + private lateinit var profileViewModel: ProfileViewModel + + @Before + fun setup() { + profileViewModel = ProfileViewModel(fetchUserData, authUseCase) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + val userForFirebase = UserForFirebase( + fullName = "teste full name", + username = "teste username", + email = "teste email" + ) + + val repos = listOf( + Repos( + id = 1, + node_id = "teste node id", + name = "teste name", + full_name = "teste full name", + private = false, + description = "teste description", + url = "teste url", + created_at = "teste created at", + updated_at = "teste updated at", + pushed_at = "teste pushed at", + clone_url = "teste clone url", + ) + ) + + @Test + fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + profileViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do GitHub deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("") + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadUserFromGit("") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + profileViewModel.loadUserFromFirebase() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadUserFromFirebase() + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForFirebase == UserForFirebase()) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar sucesso e buscar os dados do repositorios deve retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Success(userForGit) + + coEvery{ + fetchUserData.loadRepos(userForGit.login.let{"teste userName"}) + } returns Result.Success(repos) + + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == userForGit) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + fetchUserData.loadRepos(any()) + } + } + + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar erro`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar sucesso e buscar os dados do repositorios deve retornar erro`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Success(userForGit) + + coEvery{ + fetchUserData.loadRepos(userForGit.login.let{"teste userName"}) + } returns Result.Error("Erro ao buscar repositorios") + + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == userForGit) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Success(repos) + + profileViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Error("Erro ao buscar repositorios") + + profileViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } +} \ No newline at end of file From c521f9eae89a610ddef0fca82679934e2b553637 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 17:35:12 -0300 Subject: [PATCH 7/8] Refatorado: Movido e renomeado testes de ViewModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Movido os arquivos de teste `AuthTestes.kt`, `ProfileTestes.kt` e `HomeTestes.kt` para o pacote `com.delecrode.devhub.viewModel` para centralizar os testes de ViewModel. - Adicionado `ForgotTestes.kt` com testes para a funcionalidade de redefinição de senha. - Renomeado o campo `success` para `isSuccess` no `ForgotPasswordState` e atualizado seus usos para manter a consistência com outros estados. --- .../ui/forgot/ForgotPasswordScreen.kt | 4 +- .../ui/forgot/ForgotPasswordState.kt | 2 +- .../ui/forgot/ForgotPasswordViewModel.kt | 2 +- .../devhub/viewModel/{auth => }/AuthTestes.kt | 2 +- .../devhub/viewModel/ForgotTestes.kt | 109 ++++++++++++++++++ .../devhub/viewModel/{home => }/HomeTestes.kt | 2 +- .../viewModel/{profile => }/ProfileTestes.kt | 10 +- 7 files changed, 120 insertions(+), 11 deletions(-) rename app/src/test/java/com/delecrode/devhub/viewModel/{auth => }/AuthTestes.kt (99%) create mode 100644 app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt rename app/src/test/java/com/delecrode/devhub/viewModel/{home => }/HomeTestes.kt (99%) rename app/src/test/java/com/delecrode/devhub/viewModel/{profile => }/ProfileTestes.kt (97%) diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt index 27700a0..7073440 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt @@ -46,8 +46,8 @@ fun ForgotPasswordScreen(navController: NavController, viewModel: ForgotPassword var email by remember { mutableStateOf("") } - LaunchedEffect(state.success) { - if (state.success) { + LaunchedEffect(state.isSuccess) { + if (state.isSuccess) { navController.navigate(AppDestinations.Login.route){ popUpTo(AppDestinations.Login.route){ inclusive = true diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt index d4e4ece..3afbfb8 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt @@ -3,6 +3,6 @@ package com.delecrode.devhub.presentation.ui.forgot data class ForgotPasswordState( val isLoading: Boolean = false, val error: String? = null, - val success: Boolean = false, + val isSuccess: Boolean = false, val emailError: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt index e3213eb..ad4961a 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt @@ -32,7 +32,7 @@ class ForgotPasswordViewModel(private val authRepository: AuthRepository) : View is Result.Success -> { _state.value = _state.value.copy( isLoading = false, - success = true + isSuccess = true ) } diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt similarity index 99% rename from app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt rename to app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt index ca46010..7345f20 100644 --- a/app/src/test/java/com/delecrode/devhub/viewModel/auth/AuthTestes.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt @@ -1,4 +1,4 @@ -package com.delecrode.devhub.viewModel.auth +package com.delecrode.devhub.viewModel import com.delecrode.devhub.MainDispatcherRule import com.delecrode.devhub.data.utils.Result diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt new file mode 100644 index 0000000..2c0a00d --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt @@ -0,0 +1,109 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserAuth +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class ForgotTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private lateinit var viewModel: ForgotPasswordViewModel + + @Before + fun setup() { + viewModel = ForgotPasswordViewModel(authRepository) + } + + @Test + fun `quando forgotPassword sucesso deve atualizar state com sucesso`() = runTest { + + coEvery { + authRepository.forgotPassword("teste@email.com") + } returns Result.Success(Unit) + + viewModel.forgotPassword("teste@email.com") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + + coVerify(exactly = 1) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando forgotPassword falhar deve atualizar state erro`() = runTest{ + coEvery { + authRepository.forgotPassword("teste@email.com") + } returns Result.Error("Erro ao enviar e-mail") + + viewModel.forgotPassword("teste@email.com") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == "Erro ao enviar e-mail") + + coVerify(exactly = 1) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando email invalido na validacao local deve atualizar state com erro no email`() = runTest{ + viewModel.forgotPassword("teste") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + assert(state.emailError == "Email inválido") + + coVerify(exactly = 0) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando email vazio na validacao local deve atualizar state com erro no email`() = runTest{ + viewModel.forgotPassword("") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + assert(state.emailError == "Email é obrigatório") + + coVerify(exactly = 0) { + authRepository.forgotPassword(any()) + } + + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt similarity index 99% rename from app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt rename to app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt index 5f1bb48..bfa3b6e 100644 --- a/app/src/test/java/com/delecrode/devhub/viewModel/home/HomeTestes.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt @@ -1,4 +1,4 @@ -package com.delecrode.devhub.viewModel.home +package com.delecrode.devhub.viewModel import HomeViewModel import com.delecrode.devhub.MainDispatcherRule diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt similarity index 97% rename from app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt rename to app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt index 642573b..e1bd36e 100644 --- a/app/src/test/java/com/delecrode/devhub/viewModel/profile/ProfileTestes.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt @@ -1,4 +1,4 @@ -package com.delecrode.devhub.viewModel.profile +package com.delecrode.devhub.viewModel import com.delecrode.devhub.MainDispatcherRule import com.delecrode.devhub.data.utils.Result @@ -157,8 +157,8 @@ class ProfileTestes { fetchUserData.loadUserFromGit(userForFirebase.username) } returns Result.Success(userForGit) - coEvery{ - fetchUserData.loadRepos(userForGit.login.let{"teste userName"}) + coEvery { + fetchUserData.loadRepos(userForGit.login.let { "teste userName" }) } returns Result.Success(repos) @@ -217,8 +217,8 @@ class ProfileTestes { fetchUserData.loadUserFromGit(userForFirebase.username) } returns Result.Success(userForGit) - coEvery{ - fetchUserData.loadRepos(userForGit.login.let{"teste userName"}) + coEvery { + fetchUserData.loadRepos(userForGit.login.let { "teste userName" }) } returns Result.Error("Erro ao buscar repositorios") From dddb5e63ac5434fa166029331d894cbb940b3b28 Mon Sep 17 00:00:00 2001 From: guilh Date: Fri, 19 Dec 2025 19:07:19 -0300 Subject: [PATCH 8/8] Adicionado testes para RegisterViewModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Criado arquivo de teste `RegisterTestes.kt` para o `RegisterViewModel`. - Implementados testes para validar o nome de usuário do GitHub: - Teste de sucesso para um usuário válido. - Teste de erro para um usuário inexistente. - Teste de erro para um campo de usuário vazio. - Removido import não utilizado em `ForgotTestes.kt`. --- .../devhub/viewModel/ForgotTestes.kt | 1 - .../devhub/viewModel/RegisterTestes.kt | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt index 2c0a00d..41c5e53 100644 --- a/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt +++ b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt @@ -2,7 +2,6 @@ package com.delecrode.devhub.viewModel import com.delecrode.devhub.MainDispatcherRule import com.delecrode.devhub.data.utils.Result -import com.delecrode.devhub.domain.model.UserAuth import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel import io.mockk.coEvery diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt new file mode 100644 index 0000000..e94611a --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt @@ -0,0 +1,100 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.presentation.ui.register.RegisterViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class RegisterTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private val userRepository: UserRepository = mockk() + private lateinit var viewModel: RegisterViewModel + + @Before + fun setup() { + viewModel = RegisterViewModel(authRepository, userRepository) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + @Test + fun `quando validar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Success(userForGit) + + viewModel.validateGithubUsername("teste userName") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(state.usernameSuccess) + assert(state.usernameError == null) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getUserForGitHub("teste userName") + } + } + + @Test + fun `quando validar um usuario que não existe no GitHub deve retornar erro`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste de erro") + } returns Result.Error("Usuário não existe no GitHub") + + viewModel.validateGithubUsername("teste de erro") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(!state.isSuccess) + assert(state.usernameError == "Usuário não existe no GitHub") + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } + + @Test + fun `quando validar um usuario vazio no GitHub deve retornar erro`() = runTest { + viewModel.validateGithubUsername("") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(!state.isSuccess) + assert(state.usernameError == "Username é obrigatório") + + coVerify(exactly = 0) { + userRepository.getUserForGitHub(any()) + } + } +} \ No newline at end of file