Skip to content

Commit

Permalink
Merge pull request #18 from felipecastilhos/feature/add_repository_tests
Browse files Browse the repository at this point in the history
Adicionando teste a Repositórios
  • Loading branch information
felipecastilhos committed Aug 26, 2022
2 parents d27a781 + 48418e1 commit 78b78f6
Show file tree
Hide file tree
Showing 17 changed files with 211 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.felipecastilhos.pokedexandroid.core.datasource

import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.DataSourceException
import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.DataSourceError

/**
* Resource is an abstraction for data fetch
Expand All @@ -16,7 +16,7 @@ sealed class Resource<out T> {
* Resource data fetch didn't work
* @param exception was the error that occurred fetching data
*/
data class Error(val exception: DataSourceException) : Resource<Nothing>()
data class Error(val exception: DataSourceError) : Resource<Nothing>()

/**
* Resource is loading, none result was returned yet.
Expand All @@ -35,7 +35,7 @@ inline fun <T : Any> Resource<T>.onSuccess(action: (T) -> Unit): Resource<T> {
/**
* Extension to handle resouce on error data fetch
*/
inline fun <T : Any> Resource<T>.onError(action: (DataSourceException) -> Unit): Resource<T> {
inline fun <T : Any> Resource<T>.onError(action: (DataSourceError) -> Unit): Resource<T> {
if (this is Resource.Error) action(exception)
return this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import com.apollographql.apollo.api.Error
* Data source exception
* @param messageResource message for the error
*/
sealed class DataSourceException(
sealed class DataSourceError(
val messageResource: Any?
) : RuntimeException() {
) {
/**
* Unexpected error
* @param messageResource resource for the message
*/
class Unexpected(messageResource: Int) : DataSourceException(messageResource)
class Unexpected(messageResource: Int) : DataSourceError(messageResource)

/**
* Server error
* @param error that occurred
*/
class Server(error: Error?) : DataSourceException(error)
class Server(error: Error?) : DataSourceError(error)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,23 @@ package com.github.felipecastilhos.pokedexandroid.core.network
import com.apollographql.apollo.api.Response
import com.github.felipecastilhos.pokedexandroid.R
import com.github.felipecastilhos.pokedexandroid.core.datasource.Resource
import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.DataSourceException
import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.DataSourceError

/**
* Maps apollo client response to datasource's resource abstraction
* @param dataTo is a map to the response data into resource data
*/
fun <T, V> Response<T>?.toResource(dataTo: (T?) -> V): Resource<V?> {
return try {
when {
this == null -> {
Resource.Error(DataSourceException.Unexpected(R.string.server_unexpected_error))
}
this.hasErrors() -> {
Resource.Error(DataSourceException.Server(errors?.first()))
}
else -> {
Resource.Success(dataTo(data))
}
return when {
this == null -> {
Resource.Error(DataSourceError.Unexpected(R.string.server_unexpected_error))
}
this.hasErrors() -> {
Resource.Error(DataSourceError.Server(errors?.first()))
}
else -> {
Resource.Success(dataTo(data))
}
} catch (e: Exception) {
Resource.Error(DataSourceException.Unexpected(R.string.server_unexpected_error))
}
}
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.felipecastilhos.pokedexandroid.features.home.domain.repository
package com.github.felipecastilhos.pokedexandroid.features.home.data.datasource

import com.github.felipecastilhos.pokedexandroid.GetPokemonQuery
import com.github.felipecastilhos.pokedexandroid.core.logs.LogHandler
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.github.felipecastilhos.pokedexandroid.features.home.data.datasource

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.coroutines.await
import com.github.felipecastilhos.pokedexandroid.GetPokemonQuery
import com.github.felipecastilhos.pokedexandroid.core.coroutines.DispatcherProvider
import com.github.felipecastilhos.pokedexandroid.core.datasource.Resource
import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.RemoteDataSource
import com.github.felipecastilhos.pokedexandroid.core.network.toResource
import com.github.felipecastilhos.pokedexandroid.features.home.domain.models.Pokemon
import kotlinx.coroutines.withContext
import javax.inject.Inject

/**
* [PokemonDataSource] is an interface for all remote data source queries
*/
abstract class PokemonDataSource : RemoteDataSource {
abstract suspend fun search(): Resource<Pokemon?>
}

/**
* Implementation of [PokemonDataSource] searching in a GraphQl API
*/
class PokemonGraphQlDataSource @Inject constructor(
private val apolloClient: ApolloClient,
private val dispatcherProvider: DispatcherProvider
) :
PokemonDataSource() {

override suspend fun search(): Resource<Pokemon?> = withContext(dispatcherProvider.io) {
return@withContext apolloClient.query(GetPokemonQuery())?.await()
.toResource { it?.getPokemon?.mapToDomainModel() }
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.github.felipecastilhos.pokedexandroid.features.home.di

import com.apollographql.apollo.ApolloClient
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.HomeRemoteDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.HomeRemoteGraphQlDataSourceExecutor
import com.github.felipecastilhos.pokedexandroid.features.home.domain.repository.PokemonRemoteDataRepositoryExecutor
import com.github.felipecastilhos.pokedexandroid.core.coroutines.DispatcherProvider
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.PokemonDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.PokemonGraphQlDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.domain.repository.DefaultPokemonRemoteDataRepository
import com.github.felipecastilhos.pokedexandroid.features.home.domain.repository.PokemonRepository
import com.github.felipecastilhos.pokedexandroid.features.home.domain.usecase.PokemonUseCase
import dagger.Module
Expand All @@ -22,9 +23,10 @@ class HomeModule {
*/
@Provides
fun providesHomeRemoteDataSourceExecutor(
apolloClient: ApolloClient
): HomeRemoteDataSource {
return HomeRemoteGraphQlDataSourceExecutor(apolloClient)
apolloClient: ApolloClient,
dispatcherProvider: DispatcherProvider
): PokemonDataSource {
return PokemonGraphQlDataSource(apolloClient, dispatcherProvider)
}

/**
Expand All @@ -33,17 +35,17 @@ class HomeModule {
*/
@Provides
fun providesPokemonRepository(
homeRemoteGraphQlDataSourceExecutor: HomeRemoteGraphQlDataSourceExecutor
homeRemoteGraphQlDataSourceExecutor: PokemonGraphQlDataSource
): PokemonRepository {
return PokemonRemoteDataRepositoryExecutor(homeRemoteGraphQlDataSourceExecutor)
return DefaultPokemonRemoteDataRepository(homeRemoteGraphQlDataSourceExecutor)
}

/**
* Provides the [PokemonUseCase] for view model business logic
* @param pokemonRemoteDataRepository contains all pokemon related data
*/
@Provides
fun providesPokemonUseCase(pokemonRemoteDataRepository: PokemonRemoteDataRepositoryExecutor): PokemonUseCase {
fun providesPokemonUseCase(pokemonRemoteDataRepository: DefaultPokemonRemoteDataRepository): PokemonUseCase {
return PokemonUseCase(pokemonRemoteDataRepository)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum class PokemonTypes {
Fire,
Water,
Glass,
Eletric,
Electric,
Psychic,
Ice,
Dragon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.github.felipecastilhos.pokedexandroid.features.home.domain.repository

import com.github.felipecastilhos.pokedexandroid.GetPokemonQuery
import com.github.felipecastilhos.pokedexandroid.core.datasource.Resource
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.HomeRemoteDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.PokemonDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.domain.models.Pokemon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

/**
Expand All @@ -15,33 +12,20 @@ interface PokemonRepository {
/**
* Query for all data of a single pokemon
*/
suspend fun search(): Flow<Resource<Pokemon?>>
suspend fun search(): Resource<Pokemon?>
}

/**
* Remote pokemon data repository
* @param homeRemoteDataSource for query pokemon data
* @param pokemonDataSource for query pokemon data
*/
class PokemonRemoteDataRepositoryExecutor @Inject constructor(
private val homeRemoteDataSource: HomeRemoteDataSource
class DefaultPokemonRemoteDataRepository @Inject constructor(
private val pokemonDataSource: PokemonDataSource
) :
PokemonRepository {
/**
* Query for all data of a single pokemon
*/
override suspend fun search(): Flow<Resource<Pokemon?>> =
homeRemoteDataSource.search().mapToDomainFlow()
override suspend fun search() = pokemonDataSource.search()
}

/**
* Map Apollo Client GraphQl Result to domain abstraction flow
*/
fun Flow<Resource<GetPokemonQuery.GetPokemon?>>.mapToDomainFlow(): Flow<Resource<Pokemon?>> {
return map {
when (it) {
is Resource.Error -> it
Resource.Loading -> Resource.Loading
is Resource.Success -> Resource.Success(it.data?.mapToDomainModel())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PokemonUseCase @Inject constructor(
/**
* Retrieve all pokemon data
*/
suspend fun search(): Flow<Resource<Pokemon?>> {
suspend fun search(): Resource<Pokemon?> {
return pokemonRepository.search()
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.github.felipecastilhos.pokedexandroid.features.home.domain.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.felipecastilhos.pokedexandroid.core.coroutines.DispatcherProvider
import com.github.felipecastilhos.pokedexandroid.core.datasource.Resource
import com.github.felipecastilhos.pokedexandroid.core.logs.LogHandler
import com.github.felipecastilhos.pokedexandroid.core.viewmodels.CoroutineViewModel
import com.github.felipecastilhos.pokedexandroid.features.home.domain.models.Pokemon
import com.github.felipecastilhos.pokedexandroid.features.home.domain.usecase.PokemonUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -20,12 +20,11 @@ import javax.inject.Inject
@HiltViewModel
class PokedexHomeViewModel @Inject constructor(
private val pokemonUseCase: PokemonUseCase,
dispatcherProvider: DispatcherProvider
) :
CoroutineViewModel(dispatcherProvider) {
protected val _stateFlow: MutableStateFlow<Resource<Pokemon?>> by lazy {
private val dispatcherProvider: DispatcherProvider
) : ViewModel() {
private val _stateFlow: MutableStateFlow<Resource<Pokemon?>> by lazy {
MutableStateFlow<Resource<Pokemon?>>(Resource.Loading).apply {
launchInIoScope {
viewModelScope.launch(dispatcherProvider.main) {
searchPokemon()
}
}
Expand All @@ -35,15 +34,8 @@ class PokedexHomeViewModel @Inject constructor(
/**
* Query pokemon data
*/
suspend fun searchPokemon(): Flow<Resource<Pokemon?>> {
viewModelScope.launch {
LogHandler.d("Searching Dragonite")
pokemonUseCase.search().collect {
LogHandler.d("Dragonite located")
_stateFlow.emit(it)
}
}

return pokemonUseCase.search()
suspend fun searchPokemon() {
LogHandler.d("Searching Dragonite")
_stateFlow.emit(pokemonUseCase.search())
}
}
4 changes: 2 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources>
<string name="app_name">Pokédex Android</string>

<string name="server_unexpected_error">"Sem resposta do servidor</string>
<string name="server_no_response">"Ops, algo deu errado</string>
<string name="server_unexpected_error">Ops, algo deu errado</string>
<string name="server_no_response">Sem conexão com o servidor, tente novamente mais tarde</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.github.felipecastilhos.pokedexandroid

import com.github.felipecastilhos.pokedexandroid.core.datasource.Resource
import com.github.felipecastilhos.pokedexandroid.core.datasource.remote.DataSourceError
import com.github.felipecastilhos.pokedexandroid.features.home.data.datasource.PokemonDataSource
import com.github.felipecastilhos.pokedexandroid.features.home.domain.models.Pokemon

class FakePokemonDataSource(private val pokemon: Pokemon) : PokemonDataSource() {
override suspend fun search(): Resource<Pokemon> {
return Resource.Success(pokemon)
}
}

class FakeFailingPokemonDataSource : PokemonDataSource() {
override suspend fun search(): Resource<Pokemon?> {
return Resource.Error(exception = DataSourceError.Unexpected(R.string.server_unexpected_error))
}
}

0 comments on commit 78b78f6

Please sign in to comment.