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 {