From 1b8d0e836c24ec41c9a9197135d923aadd1ed2f0 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 21:10:35 +0300 Subject: [PATCH 1/6] Separate Hilt and Koin into their own product flavours --- app/build.gradle | 37 ++++++- app/src/hilt/AndroidManifest.xml | 22 +++++ .../test/showcase/TestShowcaseApplication.kt | 13 +++ .../org/fnives/test/showcase/di/AppModule.kt | 53 ++++++++++ .../test/showcase/ui/IntentCoordinator.kt | 14 +++ .../test/showcase/ui/ViewModelDelegate.kt | 12 +++ .../test/showcase/ui/auth/HiltAuthActivity.kt | 12 +++ .../test/showcase/ui/home/HiltMainActivity.kt | 12 +++ .../showcase/ui/splash/HiltSplashActivity.kt | 6 ++ app/src/koin/AndroidManifest.xml | 21 ++++ .../test/showcase/TestShowcaseApplication.kt | 0 .../test/showcase/di/createAppModules.kt | 2 +- .../test/showcase/ui/IntentCoordinator.kt | 14 +++ .../test/showcase/ui/ViewModelDelegate.kt | 8 ++ app/src/main/AndroidManifest.xml | 15 +-- .../session/SessionExpirationListenerImpl.kt | 11 ++- .../storage/SharedPreferencesManagerImpl.kt | 2 +- .../FavouriteContentLocalStorageImpl.kt | 3 +- .../test/showcase/ui/auth/AuthActivity.kt | 10 +- .../test/showcase/ui/auth/AuthViewModel.kt | 5 +- .../test/showcase/ui/home/MainActivity.kt | 11 ++- .../test/showcase/ui/home/MainViewModel.kt | 5 +- .../test/showcase/ui/splash/SplashActivity.kt | 13 ++- .../showcase/ui/splash/SplashViewModel.kt | 5 +- build.gradle | 13 ++- core/build.gradle | 11 +++ .../content/AddContentToFavouriteUseCase.kt | 3 +- .../core/content/ContentRepository.kt | 3 +- .../core/content/FetchContentUseCase.kt | 4 +- .../core/content/GetAllContentUseCase.kt | 3 +- .../RemoveContentFromFavouritesUseCase.kt | 3 +- .../test/showcase/core/di/hilt/CoreModule.kt | 34 +++++++ .../core/di/hilt/LoggedInModuleInject.kt | 8 ++ .../core/di/{ => koin}/createCoreModule.kt | 6 +- .../core/login/IsUserLoggedInUseCase.kt | 5 +- .../test/showcase/core/login/LoginUseCase.kt | 3 +- .../test/showcase/core/login/LogoutUseCase.kt | 15 ++- .../core/session/SessionExpirationAdapter.kt | 6 +- .../NetworkSessionLocalStorageAdapter.kt | 3 +- .../showcase/core/login/LogoutUseCaseTest.kt | 2 +- gradlescripts/versions.gradle | 2 + network/build.gradle | 2 + .../network/auth/LoginErrorConverter.kt | 3 +- .../network/auth/LoginRemoteSourceImpl.kt | 3 +- .../content/ContentRemoteSourceImpl.kt | 3 +- .../network/di/hilt/HiltNetworkModule.kt | 96 +++++++++++++++++++ .../network/di/hilt/SessionLessQualifier.kt | 6 ++ .../network/di/hilt/SessionQualifier.kt | 6 ++ .../di/{ => koin}/createNetworkmodules.kt | 3 +- .../session/AuthenticationHeaderUtils.kt | 3 +- .../network/session/SessionAuthenticator.kt | 3 +- .../LoginRemoteSourceRefreshActionImplTest.kt | 2 +- .../network/auth/LoginRemoteSourceTest.kt | 2 +- .../content/CodeKataSessionExpirationTest.kt | 2 +- .../content/ContentRemoteSourceImplTest.kt | 2 +- .../network/content/SessionExpirationTest.kt | 2 +- 56 files changed, 496 insertions(+), 72 deletions(-) create mode 100644 app/src/hilt/AndroidManifest.xml create mode 100644 app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt create mode 100644 app/src/koin/AndroidManifest.xml rename app/src/{main => koin}/java/org/fnives/test/showcase/TestShowcaseApplication.kt (100%) rename app/src/{main => koin}/java/org/fnives/test/showcase/di/createAppModules.kt (96%) create mode 100644 app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt create mode 100644 app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt create mode 100644 core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt create mode 100644 core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt rename core/src/main/java/org/fnives/test/showcase/core/di/{ => koin}/createCoreModule.kt (94%) create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt rename network/src/main/java/org/fnives/test/showcase/network/di/{ => koin}/createNetworkmodules.kt (97%) diff --git a/app/build.gradle b/app/build.gradle index a390928..7371495 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,8 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + // hilt specific + id 'dagger.hilt.android.plugin' } android { @@ -25,6 +27,17 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + flavorDimensions 'di' + productFlavors { + hilt { + dimension 'di' + applicationId "org.fnives.test.showcase.hilt" + } + koin { + dimension 'di' + applicationId "org.fnives.test.showcase.koin" + } + } buildFeatures { viewBinding true @@ -49,10 +62,17 @@ android { } } +hilt { + enableAggregatingTask = true + enableExperimentalClasspathAggregation = true +} + afterEvaluate { // making sure the :mockserver is assembled after :clean when running tests - testDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') - testReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testKoinDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testKoinReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testHiltDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testHiltReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') } dependencies { @@ -66,7 +86,14 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version" // Koin - implementation "io.insert-koin:koin-android:$koin_version" + koinImplementation "io.insert-koin:koin-android:$koin_version" + + // Hilt +// hiltImplementation "com.google.dagger:hilt-android:$hilt_version" + implementation "com.google.dagger:hilt-android:$hilt_version" +// implementation "com.google.dagger:hilt-core:$hilt_version" + kaptHilt "com.google.dagger:hilt-compiler:$hilt_version" + hiltImplementation "androidx.activity:activity-ktx:$activity_ktx_version" implementation "androidx.room:room-runtime:$androidx_room_version" kapt "androidx.room:room-compiler:$androidx_room_version" @@ -98,6 +125,8 @@ dependencies { testImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" + testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptTest "com.google.dagger:hilt-compiler:$hilt_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" androidTestImplementation "io.insert-koin:koin-test-junit4:$koin_version" @@ -111,4 +140,6 @@ dependencies { androidTestImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" + androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" } \ No newline at end of file diff --git a/app/src/hilt/AndroidManifest.xml b/app/src/hilt/AndroidManifest.xml new file mode 100644 index 0000000..d65911d --- /dev/null +++ b/app/src/hilt/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt new file mode 100644 index 0000000..a3b36fe --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt @@ -0,0 +1,13 @@ +package org.fnives.test.showcase + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class TestShowcaseApplication : Application() { + + override fun onCreate() { + super.onCreate() + + } +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt new file mode 100644 index 0000000..57fa755 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt @@ -0,0 +1,53 @@ +package org.fnives.test.showcase.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.fnives.test.showcase.core.session.SessionExpirationListener +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage +import org.fnives.test.showcase.session.SessionExpirationListenerImpl +import org.fnives.test.showcase.storage.SharedPreferencesManagerImpl +import org.fnives.test.showcase.storage.database.DatabaseInitialization +import org.fnives.test.showcase.storage.favourite.FavouriteContentLocalStorageImpl +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object AppModule { + + @Provides + fun provideBaseUrl(): String = BaseUrlProvider.get().baseUrl + + @Provides + fun enableLogging(): Boolean = true + + @Singleton + @Provides + fun provideFavouriteDao(@ApplicationContext context: Context) = + DatabaseInitialization.create(context).favouriteDao + + @Provides + fun provideSharedPreferencesManagerImpl(@ApplicationContext context: Context) = + SharedPreferencesManagerImpl.create(context) + + @Singleton + @Provides + fun provideUserDataLocalStorage( + sharedPreferencesManagerImpl: SharedPreferencesManagerImpl + ): UserDataLocalStorage = sharedPreferencesManagerImpl + + @Provides + fun provideFavouriteContentLocalStorage( + favouriteContentLocalStorageImpl: FavouriteContentLocalStorageImpl + ): FavouriteContentLocalStorage = favouriteContentLocalStorageImpl + + @Provides + internal fun bindSessionExpirationListener( + sessionExpirationListenerImpl: SessionExpirationListenerImpl + ) : SessionExpirationListener = sessionExpirationListenerImpl + +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt new file mode 100644 index 0000000..a7d3f6e --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import android.content.Context +import org.fnives.test.showcase.ui.auth.HiltAuthActivity +import org.fnives.test.showcase.ui.home.HiltMainActivity + +object IntentCoordinator { + + fun mainActivitygetStartIntent(context: Context) = + HiltMainActivity.getStartIntent(context) + + fun authActivitygetStartIntent(context: Context) = + HiltAuthActivity.getStartIntent(context) +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt new file mode 100644 index 0000000..2138fa6 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui + +import androidx.activity.ComponentActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import androidx.activity.viewModels as androidxViewModel + +inline fun ViewModelStoreOwner.viewModels(): Lazy = + when (this) { + is ComponentActivity -> androidxViewModel() + else -> throw IllegalStateException("Only supports activity viewModel for now") + } \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt new file mode 100644 index 0000000..e6df5b0 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui.auth + +import android.content.Context +import android.content.Intent +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltAuthActivity : AuthActivity() { + companion object { + fun getStartIntent(context: Context): Intent = Intent(context, HiltAuthActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt new file mode 100644 index 0000000..f6a7cdc --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui.home + +import android.content.Context +import android.content.Intent +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltMainActivity : MainActivity() { + companion object { + fun getStartIntent(context: Context): Intent = Intent(context, HiltMainActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt new file mode 100644 index 0000000..19ba159 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.ui.splash + +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltSplashActivity : SplashActivity() \ No newline at end of file diff --git a/app/src/koin/AndroidManifest.xml b/app/src/koin/AndroidManifest.xml new file mode 100644 index 0000000..72cf478 --- /dev/null +++ b/app/src/koin/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/TestShowcaseApplication.kt b/app/src/koin/java/org/fnives/test/showcase/TestShowcaseApplication.kt similarity index 100% rename from app/src/main/java/org/fnives/test/showcase/TestShowcaseApplication.kt rename to app/src/koin/java/org/fnives/test/showcase/TestShowcaseApplication.kt diff --git a/app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt b/app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt similarity index 96% rename from app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt rename to app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt index 8d05715..4c20221 100644 --- a/app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt +++ b/app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt @@ -1,6 +1,6 @@ package org.fnives.test.showcase.di -import org.fnives.test.showcase.core.di.createCoreModule +import org.fnives.test.showcase.core.di.koin.createCoreModule import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.session.SessionExpirationListenerImpl import org.fnives.test.showcase.storage.LocalDatabase diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt new file mode 100644 index 0000000..4a5170a --- /dev/null +++ b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import android.content.Context +import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.home.MainActivity + +object IntentCoordinator { + + fun mainActivitygetStartIntent(context: Context) = + MainActivity.getStartIntent(context) + + fun authActivitygetStartIntent(context: Context) = + AuthActivity.getStartIntent(context) +} \ No newline at end of file diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt new file mode 100644 index 0000000..1cca4d6 --- /dev/null +++ b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -0,0 +1,8 @@ +package org.fnives.test.showcase.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import org.koin.androidx.viewmodel.ext.android.viewModel + +inline fun ViewModelStoreOwner.viewModels(): Lazy = + viewModel() \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 09b554f..f778c8e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,24 +6,13 @@ - - - - - - - - - - + tools:ignore="AllowBackup"/> \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt b/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt index 230a521..b0fb00d 100644 --- a/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt @@ -4,15 +4,20 @@ import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper +import dagger.hilt.android.qualifiers.ApplicationContext import org.fnives.test.showcase.core.session.SessionExpirationListener -import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.IntentCoordinator +import javax.inject.Inject -class SessionExpirationListenerImpl(private val context: Context) : SessionExpirationListener { +class SessionExpirationListenerImpl @Inject constructor( + @ApplicationContext + private val context: Context +) : SessionExpirationListener { override fun onSessionExpired() { Handler(Looper.getMainLooper()).post { context.startActivity( - AuthActivity.getStartIntent(context) + IntentCoordinator.authActivitygetStartIntent(context) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) diff --git a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt index a62af66..aa4265b 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt @@ -7,7 +7,7 @@ import org.fnives.test.showcase.model.session.Session import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class SharedPreferencesManagerImpl(private val sharedPreferences: SharedPreferences) : UserDataLocalStorage { +class SharedPreferencesManagerImpl constructor(private val sharedPreferences: SharedPreferences) : UserDataLocalStorage { override var session: Session? by SessionDelegate(SESSION_KEY) diff --git a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt index ac0f9d9..4797f32 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt @@ -4,8 +4,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class FavouriteContentLocalStorageImpl(private val favouriteDao: FavouriteDao) : FavouriteContentLocalStorage { +class FavouriteContentLocalStorageImpl @Inject constructor(private val favouriteDao: FavouriteDao) : FavouriteContentLocalStorage { override fun observeFavourites(): Flow> = favouriteDao.get().map { it.map(FavouriteEntity::contentId).map(::ContentId) } diff --git a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt index 3e4157c..9e035bc 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt @@ -9,12 +9,12 @@ import androidx.core.widget.doAfterTextChanged import com.google.android.material.snackbar.Snackbar import org.fnives.test.showcase.R import org.fnives.test.showcase.databinding.ActivityAuthenticationBinding -import org.fnives.test.showcase.ui.home.MainActivity -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.IntentCoordinator +import org.fnives.test.showcase.ui.viewModels -class AuthActivity : AppCompatActivity() { +open class AuthActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -35,7 +35,7 @@ class AuthActivity : AppCompatActivity() { } viewModel.navigateToHome.observe(this) { it.consume() ?: return@observe - startActivity(MainActivity.getStartIntent(this)) + startActivity(IntentCoordinator.mainActivitygetStartIntent(this)) finishAffinity() } setContentView(binding.root) diff --git a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt index 0221f14..b5fa26f 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt @@ -4,14 +4,17 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import org.fnives.test.showcase.core.login.LoginUseCase import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.auth.LoginStatus import org.fnives.test.showcase.model.shared.Answer import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class AuthViewModel(private val loginUseCase: LoginUseCase) : ViewModel() { +@HiltViewModel +class AuthViewModel @Inject constructor(private val loginUseCase: LoginUseCase) : ViewModel() { private val _username = MutableLiveData() val username: LiveData = _username diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt index 187304a..a9bea69 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt @@ -6,17 +6,18 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import org.fnives.test.showcase.R import org.fnives.test.showcase.databinding.ActivityMainBinding import org.fnives.test.showcase.model.content.ContentId -import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.IntentCoordinator import org.fnives.test.showcase.ui.shared.VerticalSpaceItemDecoration import org.fnives.test.showcase.ui.shared.getThemePrimaryColor -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.viewModels -class MainActivity : AppCompatActivity() { +open class MainActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -45,7 +46,7 @@ class MainActivity : AppCompatActivity() { } viewModel.navigateToAuth.observe(this) { it.consume() ?: return@observe - startActivity(AuthActivity.getStartIntent(this)) + startActivity(IntentCoordinator.authActivitygetStartIntent(this)) finishAffinity() } viewModel.loading.observe(this) { diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt index 326ac01..502d543 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase @@ -16,8 +17,10 @@ import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.FavouriteContent import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class MainViewModel( +@HiltViewModel +class MainViewModel @Inject constructor( private val getAllContentUseCase: GetAllContentUseCase, private val logoutUseCase: LogoutUseCase, private val fetchContentUseCase: FetchContentUseCase, diff --git a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt index ab7ee68..48e1ce1 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt @@ -3,21 +3,20 @@ package org.fnives.test.showcase.ui.splash import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.fnives.test.showcase.R -import org.fnives.test.showcase.ui.auth.AuthActivity -import org.fnives.test.showcase.ui.home.MainActivity -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.IntentCoordinator +import org.fnives.test.showcase.ui.viewModels -class SplashActivity : AppCompatActivity() { +open class SplashActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) viewModel.navigateTo.observe(this) { val intent = when (it.consume()) { - SplashViewModel.NavigateTo.HOME -> MainActivity.getStartIntent(this) - SplashViewModel.NavigateTo.AUTHENTICATION -> AuthActivity.getStartIntent(this) + SplashViewModel.NavigateTo.HOME -> IntentCoordinator.mainActivitygetStartIntent(this) + SplashViewModel.NavigateTo.AUTHENTICATION -> IntentCoordinator.authActivitygetStartIntent(this) null -> return@observe } startActivity(intent) diff --git a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt index d9bbd43..13a1887 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt @@ -4,12 +4,15 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class SplashViewModel(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() { +@HiltViewModel +class SplashViewModel @Inject constructor(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() { private val _navigateTo = MutableLiveData>() val navigateTo: LiveData> = _navigateTo diff --git a/build.gradle b/build.gradle index c09cd78..c4df286 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,14 @@ buildscript { ext.kotlin_version = "1.5.30" ext.detekt_version = "1.18.1" + ext.hilt_version = "2.38.1" repositories { mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } } dependencies { + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0" @@ -22,6 +24,15 @@ allprojects { repositories { mavenCentral() google() + maven { + url "https://maven.pkg.github.com/fknives/ReloadableHiltModule" + credentials { + username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME") + password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN") + } + // how to get token + // https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token + } } } @@ -29,7 +40,7 @@ task clean(type: Delete) { delete rootProject.buildDir } -task unitTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]){ +task unitTests(dependsOn: ["app:testKoinDebugUnitTest", "app:testHiltDebugUnitTest", "core:test", "network:test"]){ group = 'Tests' description = 'Run all unit tests' } diff --git a/core/build.gradle b/core/build.gradle index 745175d..a66b8fc 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java-library' id 'kotlin' + id 'kotlin-kapt' } java { @@ -14,12 +15,22 @@ compileKotlin { } } +kapt { + correctErrorTypes = true +} + dependencies { api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" api project(":model") implementation project(":network") + // hilt + implementation "com.google.dagger:hilt-core:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" + implementation "org.fnives.library.reloadable.module:annotation:$reloadable_module_version" + kapt "org.fnives.library.reloadable.module:annotation-processor:$reloadable_module_version" + testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt index c5ea010..2483d1c 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.content import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class AddContentToFavouriteUseCase internal constructor( +class AddContentToFavouriteUseCase @Inject internal constructor( private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt index b243db5..143d2c8 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import org.fnives.test.showcase.core.di.hilt.LoggedInModuleInject import org.fnives.test.showcase.core.shared.Optional import org.fnives.test.showcase.core.shared.mapIntoResource import org.fnives.test.showcase.core.shared.wrapIntoAnswer @@ -14,7 +15,7 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.content.ContentRemoteSource -internal class ContentRepository(private val contentRemoteSource: ContentRemoteSource) { +internal class ContentRepository @LoggedInModuleInject constructor(private val contentRemoteSource: ContentRemoteSource) { private val mutableContentFlow = MutableStateFlow(Optional>(null)) private val requestFlow: Flow>> = flow { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt index 90c7a5b..16944f5 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt @@ -1,6 +1,8 @@ package org.fnives.test.showcase.core.content -class FetchContentUseCase internal constructor(private val contentRepository: ContentRepository) { +import javax.inject.Inject + +class FetchContentUseCase @Inject internal constructor(private val contentRepository: ContentRepository) { fun invoke() = contentRepository.fetch() } diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt index 24a4b63..56b7c72 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.FavouriteContent import org.fnives.test.showcase.model.shared.Resource +import javax.inject.Inject -class GetAllContentUseCase internal constructor( +class GetAllContentUseCase @Inject internal constructor( private val contentRepository: ContentRepository, private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt index 494af3e..4c03f90 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.content import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class RemoveContentFromFavouritesUseCase internal constructor( +class RemoveContentFromFavouritesUseCase @Inject internal constructor( private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt new file mode 100644 index 0000000..6ca5bdc --- /dev/null +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt @@ -0,0 +1,34 @@ +package org.fnives.test.showcase.core.di.hilt + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.fnives.test.showcase.core.login.LogoutUseCase +import org.fnives.test.showcase.core.session.SessionExpirationAdapter +import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule + +@InstallIn(SingletonComponent::class) +@Module +object CoreModule { + + @Provides + internal fun bindNetworkSessionLocalStorageAdapter( + networkSessionLocalStorageAdapter: NetworkSessionLocalStorageAdapter + ): NetworkSessionLocalStorage = networkSessionLocalStorageAdapter + + @Provides + internal fun bindNetworkSessionExpirationListener( + sessionExpirationAdapter: SessionExpirationAdapter + ): NetworkSessionExpirationListener = sessionExpirationAdapter + + @Provides + fun provideLogoutUseCase( + storage: UserDataLocalStorage, + reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule + ) : LogoutUseCase = LogoutUseCase(storage, reloadLoggedInModuleInjectModule) +} \ No newline at end of file diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt new file mode 100644 index 0000000..3649804 --- /dev/null +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt @@ -0,0 +1,8 @@ +package org.fnives.test.showcase.core.di.hilt + +import org.fnives.library.reloadable.module.annotation.ReloadableModule + +@ReloadableModule +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +annotation class LoggedInModuleInject \ No newline at end of file diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt similarity index 94% rename from core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt rename to core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt index d3b6bc5..5f66810 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.core.di +package org.fnives.test.showcase.core.di.koin import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase import org.fnives.test.showcase.core.content.ContentRepository @@ -14,7 +14,7 @@ import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.koin.core.module.Module import org.koin.core.scope.Scope import org.koin.dsl.module @@ -42,7 +42,7 @@ fun repositoryModule() = module { fun useCaseModule() = module { factory { LoginUseCase(get(), get()) } - factory { LogoutUseCase(get()) } + factory { LogoutUseCase(get(), null) } factory { GetAllContentUseCase(get(), get()) } factory { AddContentToFavouriteUseCase(get()) } factory { RemoveContentFromFavouritesUseCase(get()) } diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt index fd0539a..1e21dd1 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt @@ -1,8 +1,11 @@ package org.fnives.test.showcase.core.login import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import javax.inject.Inject -class IsUserLoggedInUseCase(private val userDataLocalStorage: UserDataLocalStorage) { +class IsUserLoggedInUseCase @Inject constructor( + private val userDataLocalStorage: UserDataLocalStorage +) { fun invoke(): Boolean = userDataLocalStorage.session != null } diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt index 2a91780..cf03c79 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.model.auth.LoginStatus import org.fnives.test.showcase.model.shared.Answer import org.fnives.test.showcase.network.auth.LoginRemoteSource import org.fnives.test.showcase.network.auth.model.LoginStatusResponses +import javax.inject.Inject -class LoginUseCase internal constructor( +class LoginUseCase @Inject internal constructor( private val loginRemoteSource: LoginRemoteSource, private val userDataLocalStorage: UserDataLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt index 158e7e0..7e36a78 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt @@ -1,13 +1,22 @@ package org.fnives.test.showcase.core.login -import org.fnives.test.showcase.core.di.repositoryModule +import org.fnives.test.showcase.core.di.koin.repositoryModule import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.koin.core.context.loadKoinModules +import org.koin.mp.KoinPlatformTools +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule -class LogoutUseCase(private val storage: UserDataLocalStorage) { +class LogoutUseCase( + private val storage: UserDataLocalStorage, + private val reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule? +) { suspend fun invoke() { - loadKoinModules(repositoryModule()) + if (KoinPlatformTools.defaultContext().getOrNull() == null) { + reloadLoggedInModuleInjectModule?.reload() + } else { + loadKoinModules(repositoryModule()) + } storage.session = null } } diff --git a/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt b/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt index 1947635..3571971 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt @@ -1,11 +1,11 @@ package org.fnives.test.showcase.core.session import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import javax.inject.Inject -internal class SessionExpirationAdapter( +internal class SessionExpirationAdapter @Inject constructor( private val sessionExpirationListener: SessionExpirationListener -) : - NetworkSessionExpirationListener { +) : NetworkSessionExpirationListener { override fun onSessionExpired() = sessionExpirationListener.onSessionExpired() } diff --git a/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt b/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt index a9d5d77..0d157f6 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.storage import org.fnives.test.showcase.model.session.Session import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import javax.inject.Inject -internal class NetworkSessionLocalStorageAdapter( +internal class NetworkSessionLocalStorageAdapter @Inject constructor( private val userDataLocalStorage: UserDataLocalStorage ) : NetworkSessionLocalStorage { diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt index ffb1900..776f1ab 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.core.login import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.content.ContentRepository -import org.fnives.test.showcase.core.di.createCoreModule +import org.fnives.test.showcase.core.di.koin.createCoreModule import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.model.network.BaseUrl import org.junit.jupiter.api.AfterEach diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index 4f8ed25..0d5a246 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -6,6 +6,7 @@ project.ext { androidx_livedata_version = "2.3.1" androidx_swiperefreshlayout_version = "1.1.0" androidx_room_version = "2.3.0" + activity_ktx_version = "1.3.1" coroutines_version = "1.4.3" koin_version = "3.1.2" @@ -13,6 +14,7 @@ project.ext { retrofit_version = "2.9.0" okhttp_version = "4.9.1" moshi_version = "1.12.0" + reloadable_module_version = "0.1.0" testing_androidx_code_version = "1.4.0" testing_androidx_junit_version = "1.1.3" diff --git a/network/build.gradle b/network/build.gradle index fc17ae7..7449ab7 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -18,6 +18,8 @@ dependencies { kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" api "io.insert-koin:koin-core:$koin_version" + implementation "com.google.dagger:hilt-core:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" api project(":model") diff --git a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt index 49a588b..057064e 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.network.shared.ExceptionWrapper import org.fnives.test.showcase.network.shared.exceptions.ParsingException import retrofit2.HttpException import retrofit2.Response +import javax.inject.Inject -internal class LoginErrorConverter { +internal class LoginErrorConverter @Inject constructor() { @Throws(ParsingException::class) suspend fun invoke(request: suspend () -> Response): LoginStatusResponses = diff --git a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt index 248ffde..0a5bf85 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.network.auth.model.LoginStatusResponses import org.fnives.test.showcase.network.shared.ExceptionWrapper import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException +import javax.inject.Inject -internal class LoginRemoteSourceImpl constructor( +internal class LoginRemoteSourceImpl @Inject constructor( private val loginService: LoginService, private val loginErrorConverter: LoginErrorConverter ) : LoginRemoteSource { diff --git a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt index d1daac8..4c0da1a 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt @@ -4,8 +4,9 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.ImageUrl import org.fnives.test.showcase.network.shared.ExceptionWrapper +import javax.inject.Inject -internal class ContentRemoteSourceImpl(private val contentService: ContentService) : ContentRemoteSource { +internal class ContentRemoteSourceImpl @Inject constructor(private val contentService: ContentService) : ContentRemoteSource { override suspend fun get(): List = ExceptionWrapper.wrap { diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt new file mode 100644 index 0000000..a5a6898 --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt @@ -0,0 +1,96 @@ +package org.fnives.test.showcase.network.di.hilt + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import org.fnives.test.showcase.network.auth.LoginRemoteSource +import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl +import org.fnives.test.showcase.network.auth.LoginService +import org.fnives.test.showcase.network.content.ContentRemoteSource +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl +import org.fnives.test.showcase.network.content.ContentService +import org.fnives.test.showcase.network.di.setupLogging +import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor +import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils +import org.fnives.test.showcase.network.session.SessionAuthenticator +import org.fnives.test.showcase.network.shared.PlatformInterceptor +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object HiltNetworkModule { + + @Provides + @Singleton + fun provideConverterFactory(): Converter.Factory = MoshiConverterFactory.create() + + @Provides + @Singleton + @SessionLessQualifier + fun provideSessionLessOkHttpClient(enableLogging: Boolean) = + OkHttpClient.Builder() + .addInterceptor(PlatformInterceptor()) + .setupLogging(enableLogging) + .build() + + @Provides + @Singleton + @SessionLessQualifier + fun provideSessionLessRetrofit( + baseUrl: String, + converterFactory: Converter.Factory, + @SessionLessQualifier okHttpClient: OkHttpClient + ) = Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(converterFactory) + .client(okHttpClient) + .build() + + @Provides + @Singleton + @SessionQualifier + internal fun provideSessionOkHttpClient( + @SessionLessQualifier okHttpClient: OkHttpClient, + sessionAuthenticator: SessionAuthenticator, + authenticationHeaderUtils: AuthenticationHeaderUtils + ) = + okHttpClient + .newBuilder() + .authenticator(sessionAuthenticator) + .addInterceptor(AuthenticationHeaderInterceptor(authenticationHeaderUtils)) + .build() + + @Provides + @Singleton + @SessionQualifier + fun provideSessionRetrofit( + @SessionLessQualifier retrofit: Retrofit, + @SessionQualifier okHttpClient: OkHttpClient + ) = retrofit.newBuilder() + .client(okHttpClient) + .build() + + @Provides + internal fun bindContentRemoteSource( + contentRemoteSourceImpl: ContentRemoteSourceImpl + ): ContentRemoteSource = contentRemoteSourceImpl + + @Provides + internal fun bindLoginRemoteSource( + loginRemoteSource: LoginRemoteSourceImpl + ): LoginRemoteSource = loginRemoteSource + + @Provides + internal fun provideLoginService(@SessionLessQualifier retrofit: Retrofit): LoginService = + retrofit.create(LoginService::class.java) + + @Provides + internal fun provideContentService(@SessionQualifier retrofit: Retrofit): ContentService = + retrofit.create(ContentService::class.java) + +} \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt new file mode 100644 index 0000000..46a259d --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.network.di.hilt + +import javax.inject.Qualifier + +@Qualifier +annotation class SessionLessQualifier \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt new file mode 100644 index 0000000..30ad41b --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.network.di.hilt + +import javax.inject.Qualifier + +@Qualifier +annotation class SessionQualifier \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt b/network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt similarity index 97% rename from network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt rename to network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt index b4b0e8b..2c84e77 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.network.di +package org.fnives.test.showcase.network.di.koin import okhttp3.OkHttpClient import org.fnives.test.showcase.model.network.BaseUrl @@ -9,6 +9,7 @@ import org.fnives.test.showcase.network.auth.LoginService import org.fnives.test.showcase.network.content.ContentRemoteSource import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.content.ContentService +import org.fnives.test.showcase.network.di.setupLogging import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt index cfb35c6..ae228d5 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt @@ -1,8 +1,9 @@ package org.fnives.test.showcase.network.session import okhttp3.Request +import javax.inject.Inject -internal class AuthenticationHeaderUtils(private val networkSessionLocalStorage: NetworkSessionLocalStorage) { +internal class AuthenticationHeaderUtils @Inject constructor(private val networkSessionLocalStorage: NetworkSessionLocalStorage) { fun hasToken(okhttpRequest: Request): Boolean = okhttpRequest.header(KEY) == networkSessionLocalStorage.session?.accessToken diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt index df1c411..544574c 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt @@ -6,8 +6,9 @@ import okhttp3.Request import okhttp3.Response import okhttp3.Route import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl +import javax.inject.Inject -internal class SessionAuthenticator( +internal class SessionAuthenticator @Inject constructor( private val networkSessionLocalStorage: NetworkSessionLocalStorage, private val loginRemoteSource: LoginRemoteSourceImpl, private val authenticationHeaderUtils: AuthenticationHeaderUtils, diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt index fc71d92..418d667 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.network.auth import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt index cd586bd..6d74777 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.network.auth.model.LoginStatusResponses -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt index eb2f310..625b995 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt @@ -3,7 +3,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import okhttp3.mockwebserver.MockWebServer import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.junit.jupiter.api.AfterEach diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt index f1996d2..cff87d8 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt index 0f80d3e..3450b85 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt @@ -3,7 +3,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.model.session.Session -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario From 488f020ab146f72ca96f10c45f579882745e4121 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 22:19:02 +0300 Subject: [PATCH 2/6] Add Hilt(Dagger) example unit tests for Networking --- .../showcase/core/login/LogoutUseCaseTest.kt | 2 +- network/build.gradle | 1 + .../showcase/network/TestNetworkComponent.kt | 44 +++++++ .../LoginRemoteSourceRefreshActionImplTest.kt | 98 ++++++++++++++ .../auth/hilt/LoginRemoteSourceTest.kt | 120 +++++++++++++++++ .../LoginRemoteSourceRefreshActionImplTest.kt | 3 +- .../auth/{ => koin}/LoginRemoteSourceTest.kt | 3 +- .../hilt/ContentRemoteSourceImplTest.kt | 121 ++++++++++++++++++ .../content/hilt/SessionExpirationTest.kt | 113 ++++++++++++++++ .../{ => koin}/ContentRemoteSourceImplTest.kt | 3 +- .../{ => koin}/SessionExpirationTest.kt | 3 +- 11 files changed, 506 insertions(+), 5 deletions(-) create mode 100644 network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt rename network/src/test/java/org/fnives/test/showcase/network/auth/{ => koin}/LoginRemoteSourceRefreshActionImplTest.kt (97%) rename network/src/test/java/org/fnives/test/showcase/network/auth/{ => koin}/LoginRemoteSourceTest.kt (97%) create mode 100644 network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt create mode 100644 network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt rename network/src/test/java/org/fnives/test/showcase/network/content/{ => koin}/ContentRemoteSourceImplTest.kt (97%) rename network/src/test/java/org/fnives/test/showcase/network/content/{ => koin}/SessionExpirationTest.kt (97%) diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt index 776f1ab..5e75644 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt @@ -27,7 +27,7 @@ internal class LogoutUseCaseTest : KoinTest { @BeforeEach fun setUp() { mockUserDataLocalStorage = mock() - sut = LogoutUseCase(mockUserDataLocalStorage) + sut = LogoutUseCase(mockUserDataLocalStorage, null) startKoin { modules( createCoreModule( diff --git a/network/build.gradle b/network/build.gradle index 7449ab7..ce7c233 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -30,4 +30,5 @@ dependencies { testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" + kaptTest "com.google.dagger:dagger-compiler:$hilt_version" } \ No newline at end of file diff --git a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt new file mode 100644 index 0000000..7614b21 --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt @@ -0,0 +1,44 @@ +package org.fnives.test.showcase.network + +import dagger.BindsInstance +import dagger.Component +import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceRefreshActionImplTest +import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceTest +import org.fnives.test.showcase.network.content.hilt.ContentRemoteSourceImplTest +import org.fnives.test.showcase.network.content.hilt.SessionExpirationTest +import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule +import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import javax.inject.Singleton + +@Singleton +@Component(modules = [HiltNetworkModule::class]) +interface TestNetworkComponent { + + + @Component.Builder + interface Builder { + + @BindsInstance + fun setBaseUrl(baseUrl: String): Builder + + @BindsInstance + fun setEnableLogging(enableLogging: Boolean): Builder + + @BindsInstance + fun setNetworkSessionLocalStorage(storage: NetworkSessionLocalStorage): Builder + + @BindsInstance + fun setNetworkSessionExpirationListener(listener: NetworkSessionExpirationListener): Builder + + fun build(): TestNetworkComponent + } + + fun inject(contentRemoteSourceImplTest: ContentRemoteSourceImplTest) + + fun inject(sessionExpirationTest: SessionExpirationTest) + + fun inject(loginRemoteSourceRefreshActionImplTest: LoginRemoteSourceRefreshActionImplTest) + + fun inject(loginRemoteSourceTest: LoginRemoteSourceTest) +} \ No newline at end of file diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt new file mode 100644 index 0000000..1b9e1f1 --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt @@ -0,0 +1,98 @@ +package org.fnives.test.showcase.network.auth.hilt + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.network.DaggerTestNetworkComponent +import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl +import org.fnives.test.showcase.network.mockserver.ContentData +import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions +import org.fnives.test.showcase.network.shared.exceptions.NetworkException +import org.fnives.test.showcase.network.shared.exceptions.ParsingException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.mock +import javax.inject.Inject + +@Suppress("TestFunctionName") +class LoginRemoteSourceRefreshActionImplTest { + + @Inject + internal lateinit var sut: LoginRemoteSourceImpl + private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage + + @RegisterExtension + @JvmField + val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() + private val mockServerScenarioSetup + get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup + + @BeforeEach + fun setUp() { + mockNetworkSessionLocalStorage = mock() + DaggerTestNetworkComponent.builder() + .setBaseUrl(mockServerScenarioSetupExtensions.url) + .setEnableLogging(true) + .setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage) + .setNetworkSessionExpirationListener(mock()) + .build() + .inject(this) + } + + @Test + fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session() = runBlocking { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) + val expected = ContentData.refreshSuccessResponse + + val actual = sut.refresh(ContentData.refreshSuccessResponse.refreshToken) + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly() = + runBlocking { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) + + sut.refresh(ContentData.refreshSuccessResponse.refreshToken) + val request = mockServerScenarioSetup.takeRequest() + + Assertions.assertEquals("PUT", request.method) + Assertions.assertEquals("Android", request.getHeader("Platform")) + Assertions.assertEquals(null, request.getHeader("Authorization")) + Assertions.assertEquals( + "/login/${ContentData.refreshSuccessResponse.refreshToken}", + request.path + ) + Assertions.assertEquals("", request.body.readUtf8()) + } + + @Test + fun GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.refresh(ContentData.refreshSuccessResponse.refreshToken) } + } + } + + @Test + fun GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } + } + } + + @Test + fun GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt new file mode 100644 index 0000000..94b848e --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt @@ -0,0 +1,120 @@ +package org.fnives.test.showcase.network.auth.hilt + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.model.auth.LoginCredentials +import org.fnives.test.showcase.network.DaggerTestNetworkComponent +import org.fnives.test.showcase.network.auth.LoginRemoteSource +import org.fnives.test.showcase.network.auth.model.LoginStatusResponses +import org.fnives.test.showcase.network.mockserver.ContentData +import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson +import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions +import org.fnives.test.showcase.network.shared.exceptions.NetworkException +import org.fnives.test.showcase.network.shared.exceptions.ParsingException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.mock +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode +import javax.inject.Inject + +@Suppress("TestFunctionName") +class LoginRemoteSourceTest { + + @Inject + internal lateinit var sut: LoginRemoteSource + + @RegisterExtension + @JvmField + val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() + private val mockServerScenarioSetup + get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup + + @BeforeEach + fun setUp() { + val mockNetworkSessionLocalStorage = mock() + DaggerTestNetworkComponent.builder() + .setBaseUrl(mockServerScenarioSetupExtensions.url) + .setEnableLogging(true) + .setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage) + .setNetworkSessionExpirationListener(mock()) + .build() + .inject(this) + } + + @DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned") + @Test + fun successResponseIsParsedProperly() = runBlocking { + mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) + val expected = LoginStatusResponses.Success(ContentData.loginSuccessResponse) + + val actual = sut.login(LoginCredentials("a", "b")) + + Assertions.assertEquals(expected, actual) + } + + @DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly") + @Test + fun requestProperlySetup() = runBlocking { + mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b"), false) + + sut.login(LoginCredentials("a", "b")) + val request = mockServerScenarioSetup.takeRequest() + + Assertions.assertEquals("POST", request.method) + Assertions.assertEquals("Android", request.getHeader("Platform")) + Assertions.assertEquals(null, request.getHeader("Authorization")) + Assertions.assertEquals("/login", request.path) + val loginRequest = createExpectedLoginRequestJson("a", "b") + JSONAssert.assertEquals( + loginRequest, + request.body.readUtf8(), + JSONCompareMode.NON_EXTENSIBLE + ) + } + + @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned") + @Test + fun badRequestMeansInvalidCredentials() = runBlocking { + mockServerScenarioSetup.setScenario(AuthScenario.InvalidCredentials("a", "b")) + val expected = LoginStatusResponses.InvalidCredentials + + val actual = sut.login(LoginCredentials("a", "b")) + + Assertions.assertEquals(expected, actual) + } + + @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") + @Test + fun genericErrorMeansNetworkError() { + mockServerScenarioSetup.setScenario(AuthScenario.GenericError("a", "b")) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.login(LoginCredentials("a", "b")) } + } + } + + @DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown") + @Test + fun invalidJsonMeansParsingException() { + mockServerScenarioSetup.setScenario(AuthScenario.UnexpectedJsonAsSuccessResponse("a", "b")) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.login(LoginCredentials("a", "b")) } + } + } + + @DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown") + @Test + fun malformedJsonMeansParsingException() { + mockServerScenarioSetup.setScenario(AuthScenario.MalformedJsonAsSuccessResponse("a", "b")) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.login(LoginCredentials("a", "b")) } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt index 418d667..cbf9ae7 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt @@ -1,7 +1,8 @@ -package org.fnives.test.showcase.network.auth +package org.fnives.test.showcase.network.auth.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt index 6d74777..cc1b582 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt @@ -1,8 +1,9 @@ -package org.fnives.test.showcase.network.auth +package org.fnives.test.showcase.network.auth.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.auth.LoginRemoteSource import org.fnives.test.showcase.network.auth.model.LoginStatusResponses import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt new file mode 100644 index 0000000..c3fad8c --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt @@ -0,0 +1,121 @@ +package org.fnives.test.showcase.network.content.hilt + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.TestNetworkComponent +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl +import org.fnives.test.showcase.network.di.koin.createNetworkModules +import org.fnives.test.showcase.network.mockserver.ContentData +import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions +import org.fnives.test.showcase.network.shared.exceptions.NetworkException +import org.fnives.test.showcase.network.shared.exceptions.ParsingException +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +import org.koin.test.inject +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.fnives.test.showcase.network.DaggerTestNetworkComponent +import javax.inject.Inject + +@Suppress("TestFunctionName") +class ContentRemoteSourceImplTest { + + @Inject + internal lateinit var sut: ContentRemoteSourceImpl + + @RegisterExtension + @JvmField + val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() + private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage + private val mockServerScenarioSetup + get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup + + @BeforeEach + fun setUp() { + mockNetworkSessionLocalStorage = mock() + DaggerTestNetworkComponent.builder() + .setBaseUrl(mockServerScenarioSetupExtensions.url) + .setEnableLogging(true) + .setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage) + .setNetworkSessionExpirationListener(mock()) + .build() + .inject(this) + } + + @Test + fun GIVEN_successful_response_WHEN_getting_content_THEN_its_parsed_and_returned_correctly() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Success(false)) + val expected = ContentData.contentSuccess + + val actual = sut.get() + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_successful_response_WHEN_getting_content_THEN_the_request_is_setup_properly() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Success(false), false) + + sut.get() + val request = mockServerScenarioSetup.takeRequest() + + Assertions.assertEquals("GET", request.method) + Assertions.assertEquals("Android", request.getHeader("Platform")) + Assertions.assertEquals(ContentData.loginSuccessResponse.accessToken, request.getHeader("Authorization")) + Assertions.assertEquals("/content", request.path) + Assertions.assertEquals("", request.body.readUtf8()) + } + + @Test + fun GIVEN_response_with_missing_Field_WHEN_getting_content_THEN_invalid_is_ignored_others_are_returned() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false)) + + val expected = ContentData.contentSuccessWithMissingFields + + val actual = sut.get() + + Assertions.assertEquals(expected, actual) + } + + @Test + fun GIVEN_error_response_WHEN_getting_content_THEN_network_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Error(false)) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.get() } + } + } + + @Test + fun GIVEN_unexpected_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false)) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.get() } + } + } + + @Test + fun GIVEN_malformed_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false)) + + Assertions.assertThrows(ParsingException::class.java) { + runBlocking { sut.get() } + } + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt new file mode 100644 index 0000000..7c009fa --- /dev/null +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt @@ -0,0 +1,113 @@ +package org.fnives.test.showcase.network.content.hilt + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.model.session.Session +import org.fnives.test.showcase.network.DaggerTestNetworkComponent +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl +import org.fnives.test.showcase.network.mockserver.ContentData +import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario +import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario +import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions +import org.fnives.test.showcase.network.shared.exceptions.NetworkException +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +import org.koin.test.inject +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever +import javax.inject.Inject + +@Suppress("TestFunctionName") +class SessionExpirationTest { + + @Inject + internal lateinit var sut: ContentRemoteSourceImpl + + @RegisterExtension + @JvmField + val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() + private val mockServerScenarioSetup + get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup + private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage + private lateinit var mockNetworkSessionExpirationListener: NetworkSessionExpirationListener + + @BeforeEach + fun setUp() { + mockNetworkSessionLocalStorage = mock() + mockNetworkSessionExpirationListener = mock() + DaggerTestNetworkComponent.builder() + .setBaseUrl(mockServerScenarioSetupExtensions.url) + .setEnableLogging(true) + .setNetworkSessionLocalStorage(mockNetworkSessionLocalStorage) + .setNetworkSessionExpirationListener(mockNetworkSessionExpirationListener) + .build() + .inject(this) + } + + @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens") + @Test + fun successRefreshResultsInRequestRetry() = runBlocking { + var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse + mockServerScenarioSetup.setScenario( + ContentScenario.Unauthorized(false) + .then(ContentScenario.Success(true)), + false + ) + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) + whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock } + doAnswer { sessionToReturnByMock = it.arguments[0] as Session? } + .whenever(mockNetworkSessionLocalStorage).session = anyOrNull() + + sut.get() + + mockServerScenarioSetup.takeRequest() + val refreshRequest = mockServerScenarioSetup.takeRequest() + val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest() + + Assertions.assertEquals("PUT", refreshRequest.method) + Assertions.assertEquals( + "/login/${ContentData.loginSuccessResponse.refreshToken}", + refreshRequest.path + ) + Assertions.assertEquals(null, refreshRequest.getHeader("Authorization")) + Assertions.assertEquals("Android", refreshRequest.getHeader("Platform")) + Assertions.assertEquals("", refreshRequest.body.readUtf8()) + Assertions.assertEquals( + ContentData.refreshSuccessResponse.accessToken, + retryAfterTokenRefreshRequest.getHeader("Authorization") + ) + verify(mockNetworkSessionLocalStorage, times(1)).session = + ContentData.refreshSuccessResponse + verifyZeroInteractions(mockNetworkSessionExpirationListener) + } + + @DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called") + @Test + fun failingRefreshResultsInSessionExpiration() = runBlocking { + whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) + mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(false)) + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) + + Assertions.assertThrows(NetworkException::class.java) { + runBlocking { sut.get() } + } + verify(mockNetworkSessionLocalStorage, times(3)).session + verify(mockNetworkSessionLocalStorage, times(1)).session = null + verifyNoMoreInteractions(mockNetworkSessionLocalStorage) + verify(mockNetworkSessionExpirationListener, times(1)).onSessionExpired() + } +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt index cff87d8..6edc095 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt @@ -1,7 +1,8 @@ -package org.fnives.test.showcase.network.content +package org.fnives.test.showcase.network.content.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt similarity index 97% rename from network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt rename to network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt index 3450b85..5902153 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt @@ -1,8 +1,9 @@ -package org.fnives.test.showcase.network.content +package org.fnives.test.showcase.network.content.koin import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.model.session.Session +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario From 7a0776ba9d9e0805781bd0a43ae344e328afea05 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 22:50:18 +0300 Subject: [PATCH 3/6] Add Hilt(Dagger) example unit tests for Core --- core/build.gradle | 2 + .../core/login/hilt/LogoutUseCaseTest.kt | 62 +++++++++++++++++++ .../core/login/hilt/TestCoreComponent.kt | 36 +++++++++++ .../login/{ => koin}/LogoutUseCaseTest.kt | 3 +- 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt create mode 100644 core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt rename core/src/test/java/org/fnives/test/showcase/core/login/{ => koin}/LogoutUseCaseTest.kt (95%) diff --git a/core/build.gradle b/core/build.gradle index a66b8fc..5789c72 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -36,4 +36,6 @@ dependencies { testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" + kaptTest "com.google.dagger:dagger-compiler:$hilt_version" + testImplementation "com.squareup.retrofit2:retrofit:$retrofit_version" } \ No newline at end of file diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt new file mode 100644 index 0000000..6e92568 --- /dev/null +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt @@ -0,0 +1,62 @@ +package org.fnives.test.showcase.core.login.hilt + +import kotlinx.coroutines.test.runBlockingTest +import org.fnives.test.showcase.core.content.ContentRepository +import org.fnives.test.showcase.core.di.koin.createCoreModule +import org.fnives.test.showcase.core.login.LogoutUseCase +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.model.network.BaseUrl +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.verifyZeroInteractions +import javax.inject.Inject + +@Suppress("TestFunctionName") +internal class LogoutUseCaseTest { + + @Inject + lateinit var sut: LogoutUseCase + private lateinit var mockUserDataLocalStorage: UserDataLocalStorage + private lateinit var testCoreComponent: TestCoreComponent + @Inject + lateinit var contentRepository: ContentRepository + + @BeforeEach + fun setUp() { + mockUserDataLocalStorage = mock() + testCoreComponent = DaggerTestCoreComponent.builder() + .setBaseUrl("https://a.b.com") + .setEnableLogging(true) + .setSessionExpirationListener(mock()) + .setUserDataLocalStorage(mockUserDataLocalStorage) + .build() + testCoreComponent.inject(this) + } + + @Test + fun WHEN_no_call_THEN_storage_is_not_interacted() { + verifyZeroInteractions(mockUserDataLocalStorage) + } + + @Test + fun WHEN_logout_invoked_THEN_storage_is_cleared() = runBlockingTest { + val repositoryBefore = contentRepository + + sut.invoke() + + testCoreComponent.inject(this@LogoutUseCaseTest) + val repositoryAfter = contentRepository + verify(mockUserDataLocalStorage, times(1)).session = null + verifyNoMoreInteractions(mockUserDataLocalStorage) + Assertions.assertNotSame(repositoryBefore, repositoryAfter) + } +} diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt new file mode 100644 index 0000000..4b78373 --- /dev/null +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt @@ -0,0 +1,36 @@ +package org.fnives.test.showcase.core.login.hilt + +import dagger.BindsInstance +import dagger.Component +import org.fnives.test.showcase.core.di.hilt.CoreModule +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModuleImpl +import org.fnives.test.showcase.core.session.SessionExpirationListener +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule +import javax.inject.Singleton + +@Singleton +@Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class]) +internal interface TestCoreComponent { + + + @Component.Builder + interface Builder { + + @BindsInstance + fun setBaseUrl(baseUrl: String): Builder + + @BindsInstance + fun setEnableLogging(enableLogging: Boolean): Builder + + @BindsInstance + fun setSessionExpirationListener(listener: SessionExpirationListener): Builder + + @BindsInstance + fun setUserDataLocalStorage(storage: UserDataLocalStorage): Builder + + fun build(): TestCoreComponent + } + + fun inject(logoutUseCaseTest: LogoutUseCaseTest) +} \ No newline at end of file diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt similarity index 95% rename from core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt rename to core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt index 5e75644..78574fe 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt @@ -1,8 +1,9 @@ -package org.fnives.test.showcase.core.login +package org.fnives.test.showcase.core.login.koin import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.content.ContentRepository import org.fnives.test.showcase.core.di.koin.createCoreModule +import org.fnives.test.showcase.core.login.LogoutUseCase import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.model.network.BaseUrl import org.junit.jupiter.api.AfterEach From e8d0c746b9930990bd9604ce84f53738ac3d326e Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 23:13:44 +0300 Subject: [PATCH 4/6] Add Hilt(Dagger) example of robolectric tests in app --- app/build.gradle | 9 +- .../FavouriteContentLocalStorageImplTest.kt | 97 +++++++++++++++++++ .../showcase/favourite/robolectric.properties | 4 + .../FavouriteContentLocalStorageImplTest.kt | 0 .../ReloadKoinModulesIfNecessaryTestRule.kt | 36 +++---- .../org/fnives/test/showcase/di/DITest.kt | 20 ++-- 6 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt create mode 100644 app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties rename app/src/{robolectricTest => robolectricTestKoin}/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt (100%) diff --git a/app/build.gradle b/app/build.gradle index 7371495..107f2db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,6 +51,13 @@ android { java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/robolectricTest/java" } + testHilt { + java.srcDirs += "src/robolectricTestHilt/java" + resources.srcDirs += "src/robolectricTestHilt/resources" + } + testKoin { + java.srcDirs += "src/robolectricTestKoin/java" + } } // needed for androidTest @@ -89,9 +96,7 @@ dependencies { koinImplementation "io.insert-koin:koin-android:$koin_version" // Hilt -// hiltImplementation "com.google.dagger:hilt-android:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version" -// implementation "com.google.dagger:hilt-core:$hilt_version" kaptHilt "com.google.dagger:hilt-compiler:$hilt_version" hiltImplementation "androidx.activity:activity-ktx:$activity_ktx_version" diff --git a/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt new file mode 100644 index 0000000..967962f --- /dev/null +++ b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -0,0 +1,97 @@ +package org.fnives.test.showcase.favourite + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestCoroutineDispatcher +import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage +import org.fnives.test.showcase.model.content.ContentId +import org.fnives.test.showcase.storage.database.DatabaseInitialization +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import javax.inject.Inject + +@Suppress("TestFunctionName") +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@HiltAndroidTest +internal class FavouriteContentLocalStorageImplTest { + + @get:Rule + val hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var sut : FavouriteContentLocalStorage + private lateinit var testDispatcher: TestCoroutineDispatcher + + @Before + fun setUp() { + testDispatcher = TestCoroutineDispatcher() + DatabaseInitialization.dispatcher = testDispatcher + hiltRule.inject() + } + + @Test + fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { + val expected = listOf(ContentId("a")) + + sut.markAsFavourite(ContentId("a")) + val actual = sut.observeFavourites().first() + + Assert.assertEquals(expected, actual) + } + + @Test + fun GIVEN_content_id_added_WHEN_removed_to_Favourite_THEN_it_no_longer_can_be_read_out() = + runBlocking { + val expected = listOf() + sut.markAsFavourite(ContentId("b")) + + sut.deleteAsFavourite(ContentId("b")) + val actual = sut.observeFavourites().first() + + Assert.assertEquals(expected, actual) + } + + @Test + fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() = + runBlocking { + val expected = listOf(listOf(), listOf(ContentId("a"))) + + val testDispatcher = TestCoroutineDispatcher() + val actual = async(testDispatcher) { + sut.observeFavourites().take(2).toList() + } + testDispatcher.advanceUntilIdle() + + sut.markAsFavourite(ContentId("a")) + + Assert.assertEquals(expected, actual.await()) + } + + @Test + fun GIVEN_non_empty_database_WHILE_observing_content_WHEN_favourite_removed_THEN_change_is_emitted() = + runBlocking { + val expected = listOf(listOf(ContentId("a")), listOf()) + sut.markAsFavourite(ContentId("a")) + + val testDispatcher = TestCoroutineDispatcher() + val actual = async(testDispatcher) { + sut.observeFavourites().take(2).toList() + } + testDispatcher.advanceUntilIdle() + + sut.deleteAsFavourite(ContentId("a")) + + Assert.assertEquals(expected, actual.await()) + } +} diff --git a/app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties b/app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties new file mode 100644 index 0000000..cd922e4 --- /dev/null +++ b/app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties @@ -0,0 +1,4 @@ +sdk=28 +shadows=org.fnives.test.showcase.testutils.shadow.ShadowSnackbar +instrumentedPackages=androidx.loader.content +application=dagger.hilt.android.testing.HiltTestApplication diff --git a/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt similarity index 100% rename from app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt rename to app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt index 5e65a0f..4e48eb4 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt @@ -1,33 +1,29 @@ package org.fnives.test.showcase.testutils -import androidx.test.core.app.ApplicationProvider -import org.fnives.test.showcase.TestShowcaseApplication -import org.fnives.test.showcase.di.BaseUrlProvider -import org.fnives.test.showcase.di.createAppModules +//import org.fnives.test.showcase.di.createAppModules +//import org.koin.android.ext.koin.androidContext import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -import org.koin.android.ext.koin.androidContext -import org.koin.core.context.GlobalContext -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin +//import org.koin.core.context.GlobalContext +//import org.koin.core.context.startKoin class ReloadKoinModulesIfNecessaryTestRule : TestRule { override fun apply(base: Statement, description: Description): Statement = object : Statement() { override fun evaluate() { - if (GlobalContext.getOrNull() == null) { - val application = ApplicationProvider.getApplicationContext() - startKoin { - androidContext(application) - modules(createAppModules(BaseUrlProvider.get())) - } +// if (GlobalContext.getOrNull() == null) { +// val application = ApplicationProvider.getApplicationContext() +// startKoin { +// androidContext(application) +// modules(createAppModules(BaseUrlProvider.get())) +// } } - try { - base.evaluate() - } finally { - stopKoin() - } - } +// try { +// base.evaluate() +// } finally { +// stopKoin() +// } +// } } } diff --git a/app/src/test/java/org/fnives/test/showcase/di/DITest.kt b/app/src/test/java/org/fnives/test/showcase/di/DITest.kt index 1a88d3d..df77d80 100644 --- a/app/src/test/java/org/fnives/test/showcase/di/DITest.kt +++ b/app/src/test/java/org/fnives/test/showcase/di/DITest.kt @@ -1,7 +1,6 @@ package org.fnives.test.showcase.di import android.content.Context -import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.testutils.TestMainDispatcher import org.fnives.test.showcase.ui.auth.AuthViewModel import org.fnives.test.showcase.ui.home.MainViewModel @@ -10,11 +9,8 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.koin.android.ext.koin.androidContext -import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.test.KoinTest -import org.koin.test.check.checkModules import org.koin.test.inject import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn @@ -42,20 +38,20 @@ class DITest : KoinTest { fun verifyStaticModules() { val mockContext = mock() whenever(mockContext.getSharedPreferences(anyOrNull(), anyOrNull())).doReturn(mock()) - checkModules { - androidContext(mockContext) - modules(createAppModules(BaseUrl("https://a.com/"))) - } +// checkModules { +// androidContext(mockContext) +// modules(createAppModules(BaseUrl("https://a.com/"))) +// } } @Test fun verifyViewModelModules() { val mockContext = mock() whenever(mockContext.getSharedPreferences(anyOrNull(), anyOrNull())).doReturn(mock()) - startKoin { - androidContext(mockContext) - modules(createAppModules(BaseUrl("https://a.com/"))) - } +// startKoin { +// androidContext(mockContext) +// modules(createAppModules(BaseUrl("https://a.com/"))) +// } authViewModel mainViewModel splashViewModel From e4f42baaed882e104fb2ed9e9291ccbce8327685 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sun, 19 Sep 2021 02:14:12 +0300 Subject: [PATCH 5/6] Add Hilt(Dagger) example of android/shared tests --- app/build.gradle | 11 + .../AndroidTestServerTypeConfiguration.kt | 14 + .../testutils/configuration/HiltTestRunner.kt | 12 + .../configuration/HttpsConfigurationModule.kt | 33 +++ .../AndroidTestServerTypeConfiguration.kt | 0 .../{favourite => }/robolectric.properties | 0 .../ReloadKoinModulesIfNecessaryTestRule.kt | 29 -- .../statesetup/SetupLoggedInState.kt | 62 ++--- .../fnives/test/showcase/ui/home/HomeRobot.kt | 8 +- .../test/showcase/ui/login/LoginRobot.kt | 8 +- .../test/showcase/ui/splash/SplashRobot.kt | 15 +- .../idling/NetworkSynchronization.kt | 42 +++ .../statesetup/SetupLoggedInState.kt | 30 +++ .../test/showcase/ui/ActivityClassHolder.kt | 14 + .../test/showcase/ui/home/MainActivityTest.kt | 248 ++++++++++++++++++ .../showcase/ui/login/AuthActivityTest.kt | 165 ++++++++++++ .../showcase/ui/splash/SplashActivityTest.kt | 99 +++++++ .../ReloadKoinModulesIfNecessaryTestRule.kt | 34 +++ .../idling/NetworkSynchronization.kt | 0 .../statesetup/SetupLoggedInState.kt | 31 +++ .../test/showcase/ui/ActivityClassHolder.kt | 14 + .../test/showcase/ui/home/MainActivityTest.kt | 0 .../showcase/ui/login/AuthActivityTest.kt | 0 .../showcase/ui/splash/SplashActivityTest.kt | 0 .../org/fnives/test/showcase/di/DITest.kt | 20 +- build.gradle | 2 +- .../core/login/hilt/TestCoreComponent.kt | 3 +- model/build.gradle | 4 + .../showcase}/hilt/SessionLessQualifier.kt | 2 +- .../test/showcase}/hilt/SessionQualifier.kt | 2 +- network/build.gradle | 6 +- .../network/di/hilt/BindsBaseOkHttpClient.kt | 17 ++ .../network/di/hilt/HiltNetworkModule.kt | 3 +- .../showcase/network/TestNetworkComponent.kt | 3 +- 34 files changed, 840 insertions(+), 91 deletions(-) create mode 100644 app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt create mode 100644 app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt create mode 100644 app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt rename app/src/{androidTest => androidTestKoin}/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt (100%) rename app/src/robolectricTestHilt/resources/org/fnives/test/showcase/{favourite => }/robolectric.properties (100%) delete mode 100644 app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt create mode 100644 app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt create mode 100644 app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt rename app/src/{sharedTest => sharedTestKoin}/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt (100%) create mode 100644 app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt create mode 100644 app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt rename app/src/{sharedTest => sharedTestKoin}/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt (100%) rename app/src/{sharedTest => sharedTestKoin}/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt (100%) rename app/src/{sharedTest => sharedTestKoin}/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt (100%) rename app/src/{test => testKoin}/java/org/fnives/test/showcase/di/DITest.kt (77%) rename {network/src/main/java/org/fnives/test/showcase/network/di => model/src/main/java/org/fnives/test/showcase}/hilt/SessionLessQualifier.kt (62%) rename {network/src/main/java/org/fnives/test/showcase/network/di => model/src/main/java/org/fnives/test/showcase}/hilt/SessionQualifier.kt (60%) create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt diff --git a/app/build.gradle b/app/build.gradle index 107f2db..1717061 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,6 +32,7 @@ android { hilt { dimension 'di' applicationId "org.fnives.test.showcase.hilt" + testInstrumentationRunner "org.fnives.test.showcase.testutils.configuration.HiltTestRunner" } koin { dimension 'di' @@ -47,15 +48,24 @@ android { androidTest { java.srcDirs += "src/sharedTest/java" } + androidTestHilt { + java.srcDirs += "src/sharedTestHilt/java" + } + androidTestKoin { + java.srcDirs += "src/sharedTestKoin/java" + } + test { java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/robolectricTest/java" } testHilt { + java.srcDirs += "src/sharedTestHilt/java" java.srcDirs += "src/robolectricTestHilt/java" resources.srcDirs += "src/robolectricTestHilt/resources" } testKoin { + java.srcDirs += "src/sharedTestKoin/java" java.srcDirs += "src/robolectricTestKoin/java" } } @@ -147,4 +157,5 @@ dependencies { androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" + androidTestImplementation project(":network") // hilt needs it } \ No newline at end of file diff --git a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt new file mode 100644 index 0000000..f538425 --- /dev/null +++ b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.testutils.configuration + +import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup + +object AndroidTestServerTypeConfiguration : ServerTypeConfiguration { + override val useHttps: Boolean get() = true + + override val url: String get() = "${MockServerScenarioSetup.HTTPS_BASE_URL}:${MockServerScenarioSetup.PORT}/" + + override fun invoke(mockServerScenarioSetup: MockServerScenarioSetup) { + val handshakeCertificates = mockServerScenarioSetup.clientCertificates ?: return + HttpsConfigurationModule.handshakeCertificates = handshakeCertificates + } +} diff --git a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt new file mode 100644 index 0000000..3ee4ec1 --- /dev/null +++ b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.testutils.configuration + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class HiltTestRunner : AndroidJUnitRunner() { + + override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application = + super.newApplication(cl, HiltTestApplication::class.java.name, context) +} \ No newline at end of file diff --git a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt new file mode 100644 index 0000000..7c34155 --- /dev/null +++ b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt @@ -0,0 +1,33 @@ +package org.fnives.test.showcase.testutils.configuration + +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import okhttp3.tls.HandshakeCertificates +import org.fnives.test.showcase.hilt.SessionLessQualifier +import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient +import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule +import javax.inject.Singleton + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [BindsBaseOkHttpClient::class] +) +object HttpsConfigurationModule { + + lateinit var handshakeCertificates: HandshakeCertificates + + @Provides + @Singleton + @SessionLessQualifier + fun bindsBaseOkHttpClient(enableLogging: Boolean) = + HiltNetworkModule.provideSessionLessOkHttpClient(enableLogging) + .newBuilder() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager + ) + .build() +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt b/app/src/androidTestKoin/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt similarity index 100% rename from app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt rename to app/src/androidTestKoin/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt diff --git a/app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties b/app/src/robolectricTestHilt/resources/org/fnives/test/showcase/robolectric.properties similarity index 100% rename from app/src/robolectricTestHilt/resources/org/fnives/test/showcase/favourite/robolectric.properties rename to app/src/robolectricTestHilt/resources/org/fnives/test/showcase/robolectric.properties diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt deleted file mode 100644 index 4e48eb4..0000000 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.fnives.test.showcase.testutils - -//import org.fnives.test.showcase.di.createAppModules -//import org.koin.android.ext.koin.androidContext -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -//import org.koin.core.context.GlobalContext -//import org.koin.core.context.startKoin - -class ReloadKoinModulesIfNecessaryTestRule : TestRule { - override fun apply(base: Statement, description: Description): Statement = - object : Statement() { - override fun evaluate() { -// if (GlobalContext.getOrNull() == null) { -// val application = ApplicationProvider.getApplicationContext() -// startKoin { -// androidContext(application) -// modules(createAppModules(BaseUrlProvider.get())) -// } - } -// try { -// base.evaluate() -// } finally { -// stopKoin() -// } -// } - } -} diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt index 4e978ee..fe7b3f7 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt @@ -1,31 +1,31 @@ -package org.fnives.test.showcase.testutils.statesetup - -import kotlinx.coroutines.runBlocking -import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase -import org.fnives.test.showcase.core.login.LoginUseCase -import org.fnives.test.showcase.core.login.LogoutUseCase -import org.fnives.test.showcase.model.auth.LoginCredentials -import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup -import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario -import org.koin.test.KoinTest -import org.koin.test.get - -object SetupLoggedInState : KoinTest { - - private val logoutUseCase get() = get() - private val loginUseCase get() = get() - private val isUserLoggedInUseCase get() = get() - - fun setupLogin(mockServerScenarioSetup: MockServerScenarioSetup) { - mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) - runBlocking { - loginUseCase.invoke(LoginCredentials("a", "b")) - } - } - - fun isLoggedIn() = isUserLoggedInUseCase.invoke() - - fun setupLogout() { - runBlocking { logoutUseCase.invoke() } - } -} +//package org.fnives.test.showcase.testutils.statesetup +// +//import kotlinx.coroutines.runBlocking +//import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase +//import org.fnives.test.showcase.core.login.LoginUseCase +//import org.fnives.test.showcase.core.login.LogoutUseCase +//import org.fnives.test.showcase.model.auth.LoginCredentials +//import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup +//import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +//import org.koin.test.KoinTest +//import org.koin.test.get +// +//object SetupLoggedInState : KoinTest { +// +// private val logoutUseCase get() = get() +// private val loginUseCase get() = get() +// private val isUserLoggedInUseCase get() = get() +// +// fun setupLogin(mockServerScenarioSetup: MockServerScenarioSetup) { +// mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) +// runBlocking { +// loginUseCase.invoke(LoginCredentials("a", "b")) +// } +// } +// +// fun isLoggedIn() = isUserLoggedInUseCase.invoke() +// +// fun setupLogout() { +// runBlocking { logoutUseCase.invoke() } +// } +//} diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/home/HomeRobot.kt b/app/src/sharedTest/java/org/fnives/test/showcase/ui/home/HomeRobot.kt index 4f6dbfa..67fbdb4 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/ui/home/HomeRobot.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/ui/home/HomeRobot.kt @@ -21,14 +21,14 @@ import org.fnives.test.showcase.testutils.robot.Robot import org.fnives.test.showcase.testutils.viewactions.PullToRefresh import org.fnives.test.showcase.testutils.viewactions.WithDrawable import org.fnives.test.showcase.testutils.viewactions.notIntended -import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.ActivityClassHolder import org.hamcrest.Matchers.allOf class HomeRobot : Robot { override fun init() { Intents.init() - Intents.intending(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) .respondWith(Instrumentation.ActivityResult(0, null)) } @@ -37,11 +37,11 @@ class HomeRobot : Robot { } fun assertNavigatedToAuth() = apply { - Intents.intended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) } fun assertDidNotNavigateToAuth() = apply { - notIntended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + notIntended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) } fun clickSignOut() = apply { diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/login/LoginRobot.kt b/app/src/sharedTest/java/org/fnives/test/showcase/ui/login/LoginRobot.kt index f291677..37b0bef 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/ui/login/LoginRobot.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/ui/login/LoginRobot.kt @@ -21,7 +21,7 @@ import org.fnives.test.showcase.testutils.configuration.SpecificTestConfiguratio import org.fnives.test.showcase.testutils.configuration.TestConfigurationsFactory import org.fnives.test.showcase.testutils.robot.Robot import org.fnives.test.showcase.testutils.viewactions.notIntended -import org.fnives.test.showcase.ui.home.MainActivity +import org.fnives.test.showcase.ui.ActivityClassHolder import org.hamcrest.core.IsNot.not class LoginRobot( @@ -37,7 +37,7 @@ class LoginRobot( override fun init() { Intents.init() - intending(hasComponent(MainActivity::class.java.canonicalName)) + intending(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) .respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent())) } @@ -91,10 +91,10 @@ class LoginRobot( } fun assertNavigatedToHome() = apply { - intended(hasComponent(MainActivity::class.java.canonicalName)) + intended(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) } fun assertNotNavigatedToHome() = apply { - notIntended(hasComponent(MainActivity::class.java.canonicalName)) + notIntended(hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) } } diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashRobot.kt b/app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashRobot.kt index ae89da1..c612228 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashRobot.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashRobot.kt @@ -5,16 +5,15 @@ import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers import org.fnives.test.showcase.testutils.robot.Robot import org.fnives.test.showcase.testutils.viewactions.notIntended -import org.fnives.test.showcase.ui.auth.AuthActivity -import org.fnives.test.showcase.ui.home.MainActivity +import org.fnives.test.showcase.ui.ActivityClassHolder class SplashRobot : Robot { override fun init() { Intents.init() - Intents.intending(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName)) + Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) .respondWith(Instrumentation.ActivityResult(0, null)) - Intents.intending(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + Intents.intending(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) .respondWith(Instrumentation.ActivityResult(0, null)) } @@ -23,18 +22,18 @@ class SplashRobot : Robot { } fun assertHomeIsStarted() = apply { - Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName)) + Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) } fun assertHomeIsNotStarted() = apply { - notIntended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName)) + notIntended(IntentMatchers.hasComponent(ActivityClassHolder.mainActivity().java.canonicalName)) } fun assertAuthIsStarted() = apply { - Intents.intended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + Intents.intended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) } fun assertAuthIsNotStarted() = apply { - notIntended(IntentMatchers.hasComponent(AuthActivity::class.java.canonicalName)) + notIntended(IntentMatchers.hasComponent(ActivityClassHolder.authActivity().java.canonicalName)) } } diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt new file mode 100644 index 0000000..5cef5c9 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt @@ -0,0 +1,42 @@ +package org.fnives.test.showcase.testutils.idling + +import androidx.annotation.CheckResult +import androidx.test.espresso.IdlingResource +import com.jakewharton.espresso.OkHttp3IdlingResource +import okhttp3.OkHttpClient +import org.fnives.test.showcase.hilt.SessionLessQualifier +import org.fnives.test.showcase.hilt.SessionQualifier +import javax.inject.Inject + +class NetworkSynchronization @Inject constructor( + @SessionQualifier + private val sessionOkhttpClient: OkHttpClient, + @SessionLessQualifier + private val sessionlessOkhttpClient: OkHttpClient +) { + + @CheckResult + fun registerNetworkingSynchronization(): Disposable { + val idlingResources = OkHttpClientTypes.values() + .map { it to getOkHttpClient(it) } + .associateBy { it.second.dispatcher } + .values + .map { (key, client) -> client.asIdlingResource(key.qualifier) } + .map(::IdlingResourceDisposable) + + return CompositeDisposable(idlingResources) + } + + private fun getOkHttpClient(type: OkHttpClientTypes): OkHttpClient = + when (type) { + OkHttpClientTypes.SESSION -> sessionOkhttpClient + OkHttpClientTypes.SESSIONLESS -> sessionlessOkhttpClient + } + + private fun OkHttpClient.asIdlingResource(name: String): IdlingResource = + OkHttp3IdlingResource.create(name, this) + + enum class OkHttpClientTypes(val qualifier: String) { + SESSION("SESSION-NETWORKING"), SESSIONLESS("SESSIONLESS-NETWORKING") + } +} diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt new file mode 100644 index 0000000..c4c1f71 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt @@ -0,0 +1,30 @@ +package org.fnives.test.showcase.testutils.statesetup + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase +import org.fnives.test.showcase.core.login.LoginUseCase +import org.fnives.test.showcase.core.login.LogoutUseCase +import org.fnives.test.showcase.model.auth.LoginCredentials +import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup +import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +import javax.inject.Inject + +class SetupLoggedInState @Inject constructor( + private val logoutUseCase : LogoutUseCase, + private val loginUseCase : LoginUseCase, + private val isUserLoggedInUseCase: IsUserLoggedInUseCase +) { + + fun setupLogin(mockServerScenarioSetup: MockServerScenarioSetup) { + mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) + runBlocking { + loginUseCase.invoke(LoginCredentials("a", "b")) + } + } + + fun isLoggedIn() = isUserLoggedInUseCase.invoke() + + fun setupLogout() { + runBlocking { logoutUseCase.invoke() } + } +} diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt new file mode 100644 index 0000000..e2e48a2 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import org.fnives.test.showcase.ui.auth.HiltAuthActivity +import org.fnives.test.showcase.ui.home.HiltMainActivity +import org.fnives.test.showcase.ui.splash.HiltSplashActivity + +object ActivityClassHolder { + + fun authActivity() = HiltAuthActivity::class + + fun mainActivity() = HiltMainActivity::class + + fun splashActivity() = HiltSplashActivity::class +} \ No newline at end of file diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt new file mode 100644 index 0000000..6266263 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt @@ -0,0 +1,248 @@ +package org.fnives.test.showcase.ui.home + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import org.fnives.test.showcase.model.content.FavouriteContent +import org.fnives.test.showcase.network.mockserver.ContentData +import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario +import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario +import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule +import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory +import org.fnives.test.showcase.testutils.idling.Disposable +import org.fnives.test.showcase.testutils.idling.NetworkSynchronization +import org.fnives.test.showcase.testutils.idling.loopMainThreadFor +import org.fnives.test.showcase.testutils.idling.loopMainThreadUntilIdleWithIdlingResources +import org.fnives.test.showcase.testutils.robot.RobotTestRule +import org.fnives.test.showcase.testutils.statesetup.SetupLoggedInState +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import javax.inject.Inject + +@Suppress("TestFunctionName") +@RunWith(AndroidJUnit4::class) +@HiltAndroidTest +class MainActivityTest { + + private lateinit var activityScenario: ActivityScenario + + @Rule + @JvmField + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Rule + @JvmField + val snackbarVerificationTestRule = SpecificTestConfigurationsFactory.createSnackbarVerification() + + @Rule + @JvmField + val robotRule = RobotTestRule(HomeRobot()) + private val homeRobot get() = robotRule.robot + + @Rule + @JvmField + val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule() + + @Rule + @JvmField + val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule() + + @Rule + @JvmField + val hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var setupLoggedInState: SetupLoggedInState + + @Inject + lateinit var networkSynchronization: NetworkSynchronization + + private lateinit var disposable: Disposable + + @Before + fun setUp() { + SpecificTestConfigurationsFactory.createServerTypeConfiguration() + .invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + + hiltRule.inject() + setupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + disposable = networkSynchronization.registerNetworkingSynchronization() + } + + @After + fun tearDown() { + activityScenario.moveToState(Lifecycle.State.DESTROYED) + disposable.dispose() + } + + @Test + fun GIVEN_initialized_MainActivity_WHEN_signout_is_clicked_THEN_user_is_signed_out() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Error(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.clickSignOut() + mainDispatcherTestRule.advanceUntilIdleOrActivityIsDestroyed() + + homeRobot.assertNavigatedToAuth() + Assert.assertEquals(false, setupLoggedInState.isLoggedIn()) + } + + @Test + fun GIVEN_success_response_WHEN_data_is_returned_THEN_it_is_shown_on_the_ui() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Success(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + ContentData.contentSuccess.forEach { + homeRobot.assertContainsItem(FavouriteContent(it, false)) + } + homeRobot.assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Success(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + homeRobot.clickOnContentItem(ContentData.contentSuccess.first()) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), true) + homeRobot.assertContainsItem(expectedItem) + .assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated_even_if_activity_is_recreated() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Success(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + homeRobot.clickOnContentItem(ContentData.contentSuccess.first()) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), true) + + activityScenario.moveToState(Lifecycle.State.DESTROYED) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.assertContainsItem(expectedItem) + .assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_success_response_WHEN_item_is_clicked_then_clicked_again_THEN_ui_is_updated() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Success(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + homeRobot.clickOnContentItem(ContentData.contentSuccess.first()) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + homeRobot.clickOnContentItem(ContentData.contentSuccess.first()) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + val expectedItem = FavouriteContent(ContentData.contentSuccess.first(), false) + homeRobot.assertContainsItem(expectedItem) + .assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_error_response_WHEN_loaded_THEN_error_is_Shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Error(false)) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.assertContainsNoItems() + .assertContainsError() + .assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_error_response_then_success_WHEN_retried_THEN_success_is_shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario( + ContentScenario.Error(false) + .then(ContentScenario.Success(false)) + ) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.swipeRefresh() + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loopMainThreadFor(2000L) + + ContentData.contentSuccess.forEach { + homeRobot.assertContainsItem(FavouriteContent(it, false)) + } + homeRobot.assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_success_then_error_WHEN_retried_THEN_error_is_shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario( + ContentScenario.Success(false) + .then(ContentScenario.Error(false)) + ) + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.swipeRefresh() + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loopMainThreadUntilIdleWithIdlingResources() + mainDispatcherTestRule.advanceTimeBy(1000L) + loopMainThreadFor(1000) + + homeRobot + .assertContainsError() + .assertContainsNoItems() + .assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_unauthenticated_then_success_WHEN_loaded_THEN_success_is_shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario( + ContentScenario.Unauthorized(false) + .then(ContentScenario.Success(true)) + ) + .setScenario(RefreshTokenScenario.Success) + + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + ContentData.contentSuccess.forEach { + homeRobot.assertContainsItem(FavouriteContent(it, false)) + } + homeRobot.assertDidNotNavigateToAuth() + } + + @Test + fun GIVEN_unauthenticated_then_error_WHEN_loaded_THEN_navigated_to_auth() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup + .setScenario(ContentScenario.Unauthorized(false)) + .setScenario(RefreshTokenScenario.Error) + + activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + + homeRobot.assertNavigatedToAuth() + Assert.assertEquals(false, setupLoggedInState.isLoggedIn()) + } +} diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt new file mode 100644 index 0000000..3183967 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt @@ -0,0 +1,165 @@ +package org.fnives.test.showcase.ui.login + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import org.fnives.test.showcase.R +import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule +import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory +import org.fnives.test.showcase.testutils.idling.Disposable +import org.fnives.test.showcase.testutils.idling.NetworkSynchronization +import org.fnives.test.showcase.testutils.robot.RobotTestRule +import org.fnives.test.showcase.ui.auth.HiltAuthActivity +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import javax.inject.Inject + +@Suppress("TestFunctionName") +@RunWith(AndroidJUnit4::class) +@HiltAndroidTest +class AuthActivityTest { + + private lateinit var activityScenario: ActivityScenario + + @Rule + @JvmField + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Rule + @JvmField + val snackbarVerificationTestRule = SpecificTestConfigurationsFactory.createSnackbarVerification() + + @Rule + @JvmField + val robotRule = RobotTestRule(LoginRobot()) + private val loginRobot get() = robotRule.robot + + @Rule + @JvmField + val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule() + + @Rule + @JvmField + val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule() + + @Rule + @JvmField + val hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var networkSynchronization: NetworkSynchronization + + private lateinit var disposable: Disposable + + @Before + fun setUp() { + SpecificTestConfigurationsFactory.createServerTypeConfiguration() + .invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + hiltRule.inject() + disposable = networkSynchronization.registerNetworkingSynchronization() + } + + @After + fun tearDown() { + activityScenario.moveToState(Lifecycle.State.DESTROYED) + disposable.dispose() + } + + @Test + fun GIVEN_non_empty_password_and_username_and_successful_response_WHEN_signIn_THEN_no_error_is_shown_and_navigating_to_home() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( + AuthScenario.Success( + password = "alma", + username = "banan" + ) + ) + activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) + loginRobot + .setPassword("alma") + .setUsername("banan") + .assertPassword("alma") + .assertUsername("banan") + .clickOnLogin() + .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleOrActivityIsDestroyed() + loginRobot.assertNavigatedToHome() + } + + @Test + fun GIVEN_empty_password_and_username_WHEN_signIn_THEN_error_password_is_shown() { + activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) + loginRobot + .setUsername("banan") + .assertUsername("banan") + .clickOnLogin() + .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loginRobot.assertErrorIsShown(R.string.password_is_invalid) + .assertNotNavigatedToHome() + .assertNotLoading() + } + + @Test + fun GIVEN_password_and_empty_username_WHEN_signIn_THEN_error_username_is_shown() { + activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) + loginRobot + .setPassword("banan") + .assertPassword("banan") + .clickOnLogin() + .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loginRobot.assertErrorIsShown(R.string.username_is_invalid) + .assertNotNavigatedToHome() + .assertNotLoading() + } + + @Test + fun GIVEN_password_and_username_and_invalid_credentials_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( + AuthScenario.InvalidCredentials(username = "alma", password = "banan") + ) + activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) + loginRobot + .setUsername("alma") + .setPassword("banan") + .assertUsername("alma") + .assertPassword("banan") + .clickOnLogin() + .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loginRobot.assertErrorIsShown(R.string.credentials_invalid) + .assertNotNavigatedToHome() + .assertNotLoading() + } + + @Test + fun GIVEN_password_and_username_and_error_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( + AuthScenario.GenericError(username = "alma", password = "banan") + ) + activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) + loginRobot + .setUsername("alma") + .setPassword("banan") + .assertUsername("alma") + .assertPassword("banan") + .clickOnLogin() + .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() + loginRobot.assertErrorIsShown(R.string.something_went_wrong) + .assertNotNavigatedToHome() + .assertNotLoading() + } +} diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt new file mode 100644 index 0000000..788e0a2 --- /dev/null +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt @@ -0,0 +1,99 @@ +package org.fnives.test.showcase.ui.splash + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import org.fnives.test.showcase.testutils.MockServerScenarioSetupTestRule +import org.fnives.test.showcase.testutils.configuration.SpecificTestConfigurationsFactory +import org.fnives.test.showcase.testutils.idling.Disposable +import org.fnives.test.showcase.testutils.idling.NetworkSynchronization +import org.fnives.test.showcase.testutils.robot.RobotTestRule +import org.fnives.test.showcase.testutils.statesetup.SetupLoggedInState +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.koin.test.KoinTest +import javax.inject.Inject + +@Suppress("TestFunctionName") +@RunWith(AndroidJUnit4::class) +@HiltAndroidTest +class SplashActivityTest : KoinTest { + + private var activityScenario: ActivityScenario? = null + + private val splashRobot: SplashRobot get() = robotTestRule.robot + + @Rule + @JvmField + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Rule + @JvmField + val robotTestRule = RobotTestRule(SplashRobot()) + + @Rule + @JvmField + val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule() + + @Rule + @JvmField + val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule() + + @Rule + @JvmField + val hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var setupLoggedInState: SetupLoggedInState + + @Inject + lateinit var networkSynchronization: NetworkSynchronization + + var disposable: Disposable? = null + + @Before + fun setUp() { + SpecificTestConfigurationsFactory.createServerTypeConfiguration() + .invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + hiltRule.inject() + disposable = networkSynchronization.registerNetworkingSynchronization() + } + + @After + fun tearDown() { + activityScenario?.moveToState(Lifecycle.State.DESTROYED) + disposable?.dispose() + } + + @Test + fun GIVEN_loggedInState_WHEN_opened_THEN_MainActivity_is_started() { + setupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + + activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) + + mainDispatcherTestRule.advanceTimeBy(500) + + splashRobot.assertHomeIsStarted() + .assertAuthIsNotStarted() + + setupLoggedInState.setupLogout() + } + + @Test + fun GIVEN_loggedOffState_WHEN_opened_THEN_AuthActivity_is_started() { + setupLoggedInState.setupLogout() + + activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) + + mainDispatcherTestRule.advanceTimeBy(500) + + splashRobot.assertAuthIsStarted() + .assertHomeIsNotStarted() + } +} diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt new file mode 100644 index 0000000..50b1427 --- /dev/null +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/ReloadKoinModulesIfNecessaryTestRule.kt @@ -0,0 +1,34 @@ +package org.fnives.test.showcase.testutils + +import androidx.test.core.app.ApplicationProvider +import org.fnives.test.showcase.TestShowcaseApplication +import org.fnives.test.showcase.di.BaseUrlProvider +import org.fnives.test.showcase.di.createAppModules +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.GlobalContext +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin + +class ReloadKoinModulesIfNecessaryTestRule : TestRule { + override fun apply(base: Statement, description: Description): Statement = + object : Statement() { + override fun evaluate() { + if (GlobalContext.getOrNull() == null) { + val application = + ApplicationProvider.getApplicationContext() + startKoin { + androidContext(application) + modules(createAppModules(BaseUrlProvider.get())) + } + } + try { + base.evaluate() + } finally { + stopKoin() + } + } + } +} diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt similarity index 100% rename from app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt rename to app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt new file mode 100644 index 0000000..4e978ee --- /dev/null +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt @@ -0,0 +1,31 @@ +package org.fnives.test.showcase.testutils.statesetup + +import kotlinx.coroutines.runBlocking +import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase +import org.fnives.test.showcase.core.login.LoginUseCase +import org.fnives.test.showcase.core.login.LogoutUseCase +import org.fnives.test.showcase.model.auth.LoginCredentials +import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup +import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +import org.koin.test.KoinTest +import org.koin.test.get + +object SetupLoggedInState : KoinTest { + + private val logoutUseCase get() = get() + private val loginUseCase get() = get() + private val isUserLoggedInUseCase get() = get() + + fun setupLogin(mockServerScenarioSetup: MockServerScenarioSetup) { + mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) + runBlocking { + loginUseCase.invoke(LoginCredentials("a", "b")) + } + } + + fun isLoggedIn() = isUserLoggedInUseCase.invoke() + + fun setupLogout() { + runBlocking { logoutUseCase.invoke() } + } +} diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt new file mode 100644 index 0000000..445c3b5 --- /dev/null +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.home.MainActivity +import org.fnives.test.showcase.ui.splash.SplashActivity + +object ActivityClassHolder { + + fun authActivity() = AuthActivity::class + + fun mainActivity() = MainActivity::class + + fun splashActivity() = SplashActivity::class +} \ No newline at end of file diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt similarity index 100% rename from app/src/sharedTest/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt rename to app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt similarity index 100% rename from app/src/sharedTest/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt rename to app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt similarity index 100% rename from app/src/sharedTest/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt rename to app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt diff --git a/app/src/test/java/org/fnives/test/showcase/di/DITest.kt b/app/src/testKoin/java/org/fnives/test/showcase/di/DITest.kt similarity index 77% rename from app/src/test/java/org/fnives/test/showcase/di/DITest.kt rename to app/src/testKoin/java/org/fnives/test/showcase/di/DITest.kt index df77d80..1a88d3d 100644 --- a/app/src/test/java/org/fnives/test/showcase/di/DITest.kt +++ b/app/src/testKoin/java/org/fnives/test/showcase/di/DITest.kt @@ -1,6 +1,7 @@ package org.fnives.test.showcase.di import android.content.Context +import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.testutils.TestMainDispatcher import org.fnives.test.showcase.ui.auth.AuthViewModel import org.fnives.test.showcase.ui.home.MainViewModel @@ -9,8 +10,11 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.test.KoinTest +import org.koin.test.check.checkModules import org.koin.test.inject import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn @@ -38,20 +42,20 @@ class DITest : KoinTest { fun verifyStaticModules() { val mockContext = mock() whenever(mockContext.getSharedPreferences(anyOrNull(), anyOrNull())).doReturn(mock()) -// checkModules { -// androidContext(mockContext) -// modules(createAppModules(BaseUrl("https://a.com/"))) -// } + checkModules { + androidContext(mockContext) + modules(createAppModules(BaseUrl("https://a.com/"))) + } } @Test fun verifyViewModelModules() { val mockContext = mock() whenever(mockContext.getSharedPreferences(anyOrNull(), anyOrNull())).doReturn(mock()) -// startKoin { -// androidContext(mockContext) -// modules(createAppModules(BaseUrl("https://a.com/"))) -// } + startKoin { + androidContext(mockContext) + modules(createAppModules(BaseUrl("https://a.com/"))) + } authViewModel mainViewModel splashViewModel diff --git a/build.gradle b/build.gradle index c4df286..35fcf1d 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ task unitTests(dependsOn: ["app:testKoinDebugUnitTest", "app:testHiltDebugUnitTe description = 'Run all unit tests' } -task androidTests(dependsOn: "app:connectedAndroidTest"){ +task androidTests(dependsOn: ["app:connectedKoinDebugAndroidTest", "app:connectedHiltDebugAndroidTest"]){ group = 'Tests' description = 'Run all Android tests' } diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt index 4b78373..8f658d6 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt @@ -6,11 +6,12 @@ import org.fnives.test.showcase.core.di.hilt.CoreModule import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModuleImpl import org.fnives.test.showcase.core.session.SessionExpirationListener import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule import javax.inject.Singleton @Singleton -@Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class]) +@Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class, BindsBaseOkHttpClient::class]) internal interface TestCoreComponent { diff --git a/model/build.gradle b/model/build.gradle index a1a673c..a1e605a 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -6,4 +6,8 @@ plugins { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation "javax.inject:javax.inject:1" } \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt b/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt similarity index 62% rename from network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt rename to model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt index 46a259d..30d3a3b 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt +++ b/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.network.di.hilt +package org.fnives.test.showcase.hilt import javax.inject.Qualifier diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt b/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt similarity index 60% rename from network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt rename to model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt index 30ad41b..2a70fba 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt +++ b/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.network.di.hilt +package org.fnives.test.showcase.hilt import javax.inject.Qualifier diff --git a/network/build.gradle b/network/build.gradle index ce7c233..0ac9855 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -17,9 +17,12 @@ dependencies { implementation "com.squareup.moshi:moshi:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" + + // koin api "io.insert-koin:koin-core:$koin_version" + + // hilt implementation "com.google.dagger:hilt-core:$hilt_version" - kapt "com.google.dagger:hilt-compiler:$hilt_version" api project(":model") @@ -30,5 +33,6 @@ dependencies { testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" kaptTest "com.google.dagger:dagger-compiler:$hilt_version" } \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt new file mode 100644 index 0000000..34e9ad7 --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt @@ -0,0 +1,17 @@ +package org.fnives.test.showcase.network.di.hilt + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import org.fnives.test.showcase.hilt.SessionLessQualifier + +@InstallIn(SingletonComponent::class) +@Module +abstract class BindsBaseOkHttpClient { + + @Binds + @SessionLessQualifier + abstract fun bindsSessionLess(okHttpClient: OkHttpClient) : OkHttpClient +} \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt index a5a6898..5667f2f 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt @@ -5,6 +5,8 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient +import org.fnives.test.showcase.hilt.SessionLessQualifier +import org.fnives.test.showcase.hilt.SessionQualifier import org.fnives.test.showcase.network.auth.LoginRemoteSource import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl import org.fnives.test.showcase.network.auth.LoginService @@ -31,7 +33,6 @@ object HiltNetworkModule { @Provides @Singleton - @SessionLessQualifier fun provideSessionLessOkHttpClient(enableLogging: Boolean) = OkHttpClient.Builder() .addInterceptor(PlatformInterceptor()) diff --git a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt index 7614b21..e2c0b3e 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt @@ -6,13 +6,14 @@ import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceRefreshAction import org.fnives.test.showcase.network.auth.hilt.LoginRemoteSourceTest import org.fnives.test.showcase.network.content.hilt.ContentRemoteSourceImplTest import org.fnives.test.showcase.network.content.hilt.SessionExpirationTest +import org.fnives.test.showcase.network.di.hilt.BindsBaseOkHttpClient import org.fnives.test.showcase.network.di.hilt.HiltNetworkModule import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import javax.inject.Singleton @Singleton -@Component(modules = [HiltNetworkModule::class]) +@Component(modules = [HiltNetworkModule::class, BindsBaseOkHttpClient::class]) interface TestNetworkComponent { From 8e9b14cecc37be3163953c53f362a4518ab0ba8a Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sun, 19 Sep 2021 02:19:01 +0300 Subject: [PATCH 6/6] Fix code analysis errors --- .../testutils/configuration/HiltTestRunner.kt | 2 +- .../configuration/HttpsConfigurationModule.kt | 2 +- .../test/showcase/TestShowcaseApplication.kt | 8 +------ .../org/fnives/test/showcase/di/AppModule.kt | 5 ++-- .../test/showcase/ui/IntentCoordinator.kt | 2 +- .../test/showcase/ui/ViewModelDelegate.kt | 2 +- .../test/showcase/ui/auth/HiltAuthActivity.kt | 2 +- .../test/showcase/ui/home/HiltMainActivity.kt | 2 +- .../showcase/ui/splash/HiltSplashActivity.kt | 2 +- .../test/showcase/ui/IntentCoordinator.kt | 2 +- .../test/showcase/ui/ViewModelDelegate.kt | 2 +- .../storage/SharedPreferencesManagerImpl.kt | 18 ++++++++++---- .../FavouriteContentLocalStorageImpl.kt | 5 +++- .../test/showcase/ui/home/MainActivity.kt | 1 - .../FavouriteContentLocalStorageImplTest.kt | 2 +- .../statesetup/SetupLoggedInState.kt | 24 +++++++++---------- .../statesetup/SetupLoggedInState.kt | 4 ++-- .../test/showcase/ui/ActivityClassHolder.kt | 2 +- .../test/showcase/ui/ActivityClassHolder.kt | 2 +- .../core/content/ContentRepository.kt | 4 +++- .../test/showcase/core/di/hilt/CoreModule.kt | 5 ++-- .../core/di/hilt/LoggedInModuleInject.kt | 2 +- .../test/showcase/core/login/LogoutUseCase.kt | 2 +- .../core/login/hilt/LogoutUseCaseTest.kt | 6 ----- .../core/login/hilt/TestCoreComponent.kt | 3 +-- .../showcase/hilt/SessionLessQualifier.kt | 2 +- .../test/showcase/hilt/SessionQualifier.kt | 2 +- .../content/ContentRemoteSourceImpl.kt | 4 +++- .../network/di/hilt/BindsBaseOkHttpClient.kt | 4 ++-- .../network/di/hilt/HiltNetworkModule.kt | 3 +-- .../session/AuthenticationHeaderUtils.kt | 7 ++++-- .../showcase/network/TestNetworkComponent.kt | 3 +-- .../hilt/ContentRemoteSourceImplTest.kt | 11 ++------- .../content/hilt/SessionExpirationTest.kt | 3 --- 34 files changed, 71 insertions(+), 79 deletions(-) diff --git a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt index 3ee4ec1..d2b4ee5 100644 --- a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt +++ b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HiltTestRunner.kt @@ -9,4 +9,4 @@ class HiltTestRunner : AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application = super.newApplication(cl, HiltTestApplication::class.java.name, context) -} \ No newline at end of file +} diff --git a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt index 7c34155..3bf93c8 100644 --- a/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt +++ b/app/src/androidTestHilt/java/org/fnives/test/showcase/testutils/configuration/HttpsConfigurationModule.kt @@ -30,4 +30,4 @@ object HttpsConfigurationModule { handshakeCertificates.trustManager ) .build() -} \ No newline at end of file +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt index a3b36fe..4881f5c 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt @@ -4,10 +4,4 @@ import android.app.Application import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class TestShowcaseApplication : Application() { - - override fun onCreate() { - super.onCreate() - - } -} +class TestShowcaseApplication : Application() diff --git a/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt index 57fa755..3e574ee 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt @@ -48,6 +48,5 @@ object AppModule { @Provides internal fun bindSessionExpirationListener( sessionExpirationListenerImpl: SessionExpirationListenerImpl - ) : SessionExpirationListener = sessionExpirationListenerImpl - -} \ No newline at end of file + ): SessionExpirationListener = sessionExpirationListenerImpl +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt index a7d3f6e..2abf987 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -11,4 +11,4 @@ object IntentCoordinator { fun authActivitygetStartIntent(context: Context) = HiltAuthActivity.getStartIntent(context) -} \ No newline at end of file +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt index 2138fa6..7ef6742 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -9,4 +9,4 @@ inline fun ViewModelStoreOwner.viewModels(): Lazy = when (this) { is ComponentActivity -> androidxViewModel() else -> throw IllegalStateException("Only supports activity viewModel for now") - } \ No newline at end of file + } diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt index e6df5b0..2574965 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt @@ -9,4 +9,4 @@ class HiltAuthActivity : AuthActivity() { companion object { fun getStartIntent(context: Context): Intent = Intent(context, HiltAuthActivity::class.java) } -} \ No newline at end of file +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt index f6a7cdc..94b0b5f 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt @@ -9,4 +9,4 @@ class HiltMainActivity : MainActivity() { companion object { fun getStartIntent(context: Context): Intent = Intent(context, HiltMainActivity::class.java) } -} \ No newline at end of file +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt index 19ba159..c3af781 100644 --- a/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt @@ -3,4 +3,4 @@ package org.fnives.test.showcase.ui.splash import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class HiltSplashActivity : SplashActivity() \ No newline at end of file +class HiltSplashActivity : SplashActivity() diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt index 4a5170a..86eec94 100644 --- a/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt +++ b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -11,4 +11,4 @@ object IntentCoordinator { fun authActivitygetStartIntent(context: Context) = AuthActivity.getStartIntent(context) -} \ No newline at end of file +} diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt index 1cca4d6..6ead7ed 100644 --- a/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt +++ b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -5,4 +5,4 @@ import androidx.lifecycle.ViewModelStoreOwner import org.koin.androidx.viewmodel.ext.android.viewModel inline fun ViewModelStoreOwner.viewModels(): Lazy = - viewModel() \ No newline at end of file + viewModel() diff --git a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt index aa4265b..6835182 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt @@ -7,13 +7,20 @@ import org.fnives.test.showcase.model.session.Session import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class SharedPreferencesManagerImpl constructor(private val sharedPreferences: SharedPreferences) : UserDataLocalStorage { +class SharedPreferencesManagerImpl( + private val sharedPreferences: SharedPreferences +) : UserDataLocalStorage { override var session: Session? by SessionDelegate(SESSION_KEY) - private class SessionDelegate(private val key: String) : ReadWriteProperty { + private class SessionDelegate(private val key: String) : + ReadWriteProperty { - override fun setValue(thisRef: SharedPreferencesManagerImpl, property: KProperty<*>, value: Session?) { + override fun setValue( + thisRef: SharedPreferencesManagerImpl, + property: KProperty<*>, + value: Session? + ) { if (value == null) { thisRef.sharedPreferences.edit().remove(key).apply() } else { @@ -25,7 +32,10 @@ class SharedPreferencesManagerImpl constructor(private val sharedPreferences: Sh } } - override fun getValue(thisRef: SharedPreferencesManagerImpl, property: KProperty<*>): Session? { + override fun getValue( + thisRef: SharedPreferencesManagerImpl, + property: KProperty<*> + ): Session? { val values = thisRef.sharedPreferences.getStringSet(key, null)?.toList() val accessToken = values?.firstOrNull { it.startsWith(ACCESS_TOKEN_KEY) } ?.drop(ACCESS_TOKEN_KEY.length) ?: return null diff --git a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt index 4797f32..f93416a 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt @@ -6,7 +6,10 @@ import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorag import org.fnives.test.showcase.model.content.ContentId import javax.inject.Inject -class FavouriteContentLocalStorageImpl @Inject constructor(private val favouriteDao: FavouriteDao) : FavouriteContentLocalStorage { +class FavouriteContentLocalStorageImpl @Inject constructor( + private val favouriteDao: FavouriteDao +) : FavouriteContentLocalStorage { + override fun observeFavourites(): Flow> = favouriteDao.get().map { it.map(FavouriteEntity::contentId).map(::ContentId) } diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt index a9bea69..3ec95c1 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import dagger.hilt.android.AndroidEntryPoint import org.fnives.test.showcase.R import org.fnives.test.showcase.databinding.ActivityMainBinding import org.fnives.test.showcase.model.content.ContentId diff --git a/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt index 967962f..7166b68 100644 --- a/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt +++ b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -30,7 +30,7 @@ internal class FavouriteContentLocalStorageImplTest { val hiltRule = HiltAndroidRule(this) @Inject - lateinit var sut : FavouriteContentLocalStorage + lateinit var sut: FavouriteContentLocalStorage private lateinit var testDispatcher: TestCoroutineDispatcher @Before diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt index fe7b3f7..ce25260 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt @@ -1,16 +1,16 @@ -//package org.fnives.test.showcase.testutils.statesetup +// package org.fnives.test.showcase.testutils.statesetup // -//import kotlinx.coroutines.runBlocking -//import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase -//import org.fnives.test.showcase.core.login.LoginUseCase -//import org.fnives.test.showcase.core.login.LogoutUseCase -//import org.fnives.test.showcase.model.auth.LoginCredentials -//import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup -//import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario -//import org.koin.test.KoinTest -//import org.koin.test.get +// import kotlinx.coroutines.runBlocking +// import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase +// import org.fnives.test.showcase.core.login.LoginUseCase +// import org.fnives.test.showcase.core.login.LogoutUseCase +// import org.fnives.test.showcase.model.auth.LoginCredentials +// import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup +// import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +// import org.koin.test.KoinTest +// import org.koin.test.get // -//object SetupLoggedInState : KoinTest { +// object SetupLoggedInState : KoinTest { // // private val logoutUseCase get() = get() // private val loginUseCase get() = get() @@ -28,4 +28,4 @@ // fun setupLogout() { // runBlocking { logoutUseCase.invoke() } // } -//} +// } diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt index c4c1f71..04d165d 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/statesetup/SetupLoggedInState.kt @@ -10,8 +10,8 @@ import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario import javax.inject.Inject class SetupLoggedInState @Inject constructor( - private val logoutUseCase : LogoutUseCase, - private val loginUseCase : LoginUseCase, + private val logoutUseCase: LogoutUseCase, + private val loginUseCase: LoginUseCase, private val isUserLoggedInUseCase: IsUserLoggedInUseCase ) { diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt index e2e48a2..5e1aaca 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt @@ -11,4 +11,4 @@ object ActivityClassHolder { fun mainActivity() = HiltMainActivity::class fun splashActivity() = HiltSplashActivity::class -} \ No newline at end of file +} diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt index 445c3b5..357b766 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/ActivityClassHolder.kt @@ -11,4 +11,4 @@ object ActivityClassHolder { fun mainActivity() = MainActivity::class fun splashActivity() = SplashActivity::class -} \ No newline at end of file +} diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt index 143d2c8..6b45ad3 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt @@ -15,7 +15,9 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.content.ContentRemoteSource -internal class ContentRepository @LoggedInModuleInject constructor(private val contentRemoteSource: ContentRemoteSource) { +internal class ContentRepository @LoggedInModuleInject constructor( + private val contentRemoteSource: ContentRemoteSource +) { private val mutableContentFlow = MutableStateFlow(Optional>(null)) private val requestFlow: Flow>> = flow { diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt index 6ca5bdc..67887f9 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt @@ -10,7 +10,6 @@ import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage -import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule @InstallIn(SingletonComponent::class) @Module @@ -30,5 +29,5 @@ object CoreModule { fun provideLogoutUseCase( storage: UserDataLocalStorage, reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule - ) : LogoutUseCase = LogoutUseCase(storage, reloadLoggedInModuleInjectModule) -} \ No newline at end of file + ): LogoutUseCase = LogoutUseCase(storage, reloadLoggedInModuleInjectModule) +} diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt index 3649804..5032c97 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt @@ -5,4 +5,4 @@ import org.fnives.library.reloadable.module.annotation.ReloadableModule @ReloadableModule @Target(AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.SOURCE) -annotation class LoggedInModuleInject \ No newline at end of file +annotation class LoggedInModuleInject diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt index 7e36a78..15d6033 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt @@ -1,10 +1,10 @@ package org.fnives.test.showcase.core.login +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule import org.fnives.test.showcase.core.di.koin.repositoryModule import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.koin.core.context.loadKoinModules import org.koin.mp.KoinPlatformTools -import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule class LogoutUseCase( private val storage: UserDataLocalStorage, diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt index 6e92568..20ea451 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt @@ -2,17 +2,11 @@ package org.fnives.test.showcase.core.login.hilt import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.content.ContentRepository -import org.fnives.test.showcase.core.di.koin.createCoreModule import org.fnives.test.showcase.core.login.LogoutUseCase import org.fnives.test.showcase.core.storage.UserDataLocalStorage -import org.fnives.test.showcase.model.network.BaseUrl -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import org.koin.test.KoinTest import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt index 8f658d6..f5d2997 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/TestCoreComponent.kt @@ -14,7 +14,6 @@ import javax.inject.Singleton @Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class, BindsBaseOkHttpClient::class]) internal interface TestCoreComponent { - @Component.Builder interface Builder { @@ -34,4 +33,4 @@ internal interface TestCoreComponent { } fun inject(logoutUseCaseTest: LogoutUseCaseTest) -} \ No newline at end of file +} diff --git a/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt b/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt index 30d3a3b..7760b2f 100644 --- a/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt +++ b/model/src/main/java/org/fnives/test/showcase/hilt/SessionLessQualifier.kt @@ -3,4 +3,4 @@ package org.fnives.test.showcase.hilt import javax.inject.Qualifier @Qualifier -annotation class SessionLessQualifier \ No newline at end of file +annotation class SessionLessQualifier diff --git a/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt b/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt index 2a70fba..bba6af8 100644 --- a/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt +++ b/model/src/main/java/org/fnives/test/showcase/hilt/SessionQualifier.kt @@ -3,4 +3,4 @@ package org.fnives.test.showcase.hilt import javax.inject.Qualifier @Qualifier -annotation class SessionQualifier \ No newline at end of file +annotation class SessionQualifier diff --git a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt index 4c0da1a..25d830d 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt @@ -6,7 +6,9 @@ import org.fnives.test.showcase.model.content.ImageUrl import org.fnives.test.showcase.network.shared.ExceptionWrapper import javax.inject.Inject -internal class ContentRemoteSourceImpl @Inject constructor(private val contentService: ContentService) : ContentRemoteSource { +internal class ContentRemoteSourceImpl @Inject constructor( + private val contentService: ContentService +) : ContentRemoteSource { override suspend fun get(): List = ExceptionWrapper.wrap { diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt index 34e9ad7..260f5ad 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/BindsBaseOkHttpClient.kt @@ -13,5 +13,5 @@ abstract class BindsBaseOkHttpClient { @Binds @SessionLessQualifier - abstract fun bindsSessionLess(okHttpClient: OkHttpClient) : OkHttpClient -} \ No newline at end of file + abstract fun bindsSessionLess(okHttpClient: OkHttpClient): OkHttpClient +} diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt index 5667f2f..fe8d982 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt @@ -93,5 +93,4 @@ object HiltNetworkModule { @Provides internal fun provideContentService(@SessionQualifier retrofit: Retrofit): ContentService = retrofit.create(ContentService::class.java) - -} \ No newline at end of file +} diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt index ae228d5..56601b1 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt @@ -3,13 +3,16 @@ package org.fnives.test.showcase.network.session import okhttp3.Request import javax.inject.Inject -internal class AuthenticationHeaderUtils @Inject constructor(private val networkSessionLocalStorage: NetworkSessionLocalStorage) { +internal class AuthenticationHeaderUtils @Inject constructor( + private val networkSessionLocalStorage: NetworkSessionLocalStorage +) { fun hasToken(okhttpRequest: Request): Boolean = okhttpRequest.header(KEY) == networkSessionLocalStorage.session?.accessToken fun attachToken(okhttpRequest: Request): Request = - okhttpRequest.newBuilder().header(KEY, networkSessionLocalStorage.session?.accessToken.orEmpty()).build() + okhttpRequest.newBuilder() + .header(KEY, networkSessionLocalStorage.session?.accessToken.orEmpty()).build() companion object { private const val KEY = "Authorization" diff --git a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt index e2c0b3e..9300e01 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/TestNetworkComponent.kt @@ -16,7 +16,6 @@ import javax.inject.Singleton @Component(modules = [HiltNetworkModule::class, BindsBaseOkHttpClient::class]) interface TestNetworkComponent { - @Component.Builder interface Builder { @@ -42,4 +41,4 @@ interface TestNetworkComponent { fun inject(loginRemoteSourceRefreshActionImplTest: LoginRemoteSourceRefreshActionImplTest) fun inject(loginRemoteSourceTest: LoginRemoteSourceTest) -} \ No newline at end of file +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt index c3fad8c..d1b669d 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt @@ -1,33 +1,26 @@ package org.fnives.test.showcase.network.content.hilt import kotlinx.coroutines.runBlocking -import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.TestNetworkComponent +import org.fnives.test.showcase.network.DaggerTestNetworkComponent import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl -import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import org.koin.test.KoinTest import org.koin.test.inject import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -import org.fnives.test.showcase.network.DaggerTestNetworkComponent import javax.inject.Inject @Suppress("TestFunctionName") -class ContentRemoteSourceImplTest { +class ContentRemoteSourceImplTest { @Inject internal lateinit var sut: ContentRemoteSourceImpl diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt index 7c009fa..9476715 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt @@ -11,14 +11,11 @@ import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions import org.fnives.test.showcase.network.shared.exceptions.NetworkException -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import org.koin.core.context.stopKoin -import org.koin.test.KoinTest import org.koin.test.inject import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer