diff --git a/project/app-common/build.gradle b/project/app-common/build.gradle index 322468a..7790ee0 100644 --- a/project/app-common/build.gradle +++ b/project/app-common/build.gradle @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.kapt) + alias(libs.plugins.hilt) } android { @@ -29,7 +31,21 @@ android { } } +hilt { + // This flag reduces incremental compilation times by reducing how often an incremental change causes a rebuild of the Dagger components. + // See https://dagger.dev/hilt/gradle-setup.html#aggregating-task + enableAggregatingTask = true +} + +kapt { + // If Hilt is used in a Kotlin project, then Kapt should be configured to keep the correct error types. + // See https://dagger.dev/hilt/gradle-setup.html#using-hilt-with-kotlin + correctErrorTypes = true +} + dependencies { + libsHelper.addDependencyInjectionDependencies(it) + implementation libs.kotlin coreLibraryDesugaring libs.jdk.desugar diff --git a/project/app-common/src/main/java/mobi/lab/sample/app/common/di/IdlerModule.kt b/project/app-common/src/main/java/mobi/lab/sample/app/common/di/IdlerModule.kt new file mode 100644 index 0000000..341bb63 --- /dev/null +++ b/project/app-common/src/main/java/mobi/lab/sample/app/common/di/IdlerModule.kt @@ -0,0 +1,18 @@ +package mobi.lab.sample.app.common.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import mobi.lab.sample.app.common.test.Idler +import mobi.lab.sample.app.common.test.NoOpIdler +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object IdlerModule { + + @Provides + @Singleton + internal fun provideIdler(): Idler = NoOpIdler() +} diff --git a/project/app-common/src/main/java/mobi/lab/sample/app/common/test/Idler.kt b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/Idler.kt new file mode 100644 index 0000000..ff3471f --- /dev/null +++ b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/Idler.kt @@ -0,0 +1,46 @@ +package mobi.lab.sample.app.common.test + +interface Idler { + /** + * Create a new [IdlerToken]. This does not mark the token as busy + * + * @return new [IdlerToken] + */ + fun token(): IdlerToken + + /** + * Create a new [IdlerToken] and mark it as busy. + * + * @return new [IdlerToken] + */ + fun busy(): IdlerToken + + /** + * Create a new [IdlerToken] from the given key and mark it as busy. + * + * @param key Any object to use as a value for the token. + * @return new [IdlerToken] using the key + */ + fun busy(key: Any): IdlerToken + + /** + * Mark the [IdlerToken] as busy. + * + * @param token [IdlerToken] + */ + fun busy(token: IdlerToken) + + /** + * Mark work identified by key as done. + * + * @param key Any object that identifies the work + */ + fun done(key: Any) + + /** + * Mark work identified by token as done. + * + * @param token [IdlerToken] + */ + fun done(token: IdlerToken) +} diff --git a/project/app-common/src/main/java/mobi/lab/sample/app/common/test/IdlerToken.kt b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/IdlerToken.kt new file mode 100644 index 0000000..3f2ca00 --- /dev/null +++ b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/IdlerToken.kt @@ -0,0 +1,3 @@ +package mobi.lab.sample.app.common.test + +data class IdlerToken(val key: Any) diff --git a/project/app-common/src/main/java/mobi/lab/sample/app/common/test/NoOpIdler.kt b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/NoOpIdler.kt new file mode 100644 index 0000000..52d2db4 --- /dev/null +++ b/project/app-common/src/main/java/mobi/lab/sample/app/common/test/NoOpIdler.kt @@ -0,0 +1,28 @@ +package mobi.lab.sample.app.common.test + +class NoOpIdler : Idler { + + override fun token(): IdlerToken { + return IdlerToken(Any()) + } + + override fun busy(): IdlerToken { + return IdlerToken(Any()) + } + + override fun busy(key: Any): IdlerToken { + return IdlerToken(key) + } + + override fun busy(token: IdlerToken) { + // Do nothing + } + + override fun done(key: Any) { + // Do nothing + } + + override fun done(token: IdlerToken) { + // Do nothing + } +} diff --git a/project/app/build.gradle b/project/app/build.gradle index 365da45..09ff6d1 100644 --- a/project/app/build.gradle +++ b/project/app/build.gradle @@ -53,6 +53,7 @@ android { targetSdkVersion(libs.versions.android.sdk.target.get()) minSdkVersion(libs.versions.android.sdk.min.get()) applicationId = "mobi.lab.sample" + testInstrumentationRunner = "mobi.lab.sample.util.CustomTestRunner" versionCode = project.ext.versionCode versionName = project.ext.versionName @@ -158,6 +159,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { dependencies { libsHelper.addDependencyInjectionDependencies(it) libsHelper.addUnitTestDependencies(it) + libsHelper.addInstrumentationTestDependencies(it) implementation libs.kotlin implementation libs.androidx.legacy diff --git a/project/app/src/androidTest/java/mobi/lab/sample/TestApp.kt b/project/app/src/androidTest/java/mobi/lab/sample/TestApp.kt new file mode 100644 index 0000000..85bcec2 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/TestApp.kt @@ -0,0 +1,7 @@ +package mobi.lab.sample + +import dagger.hilt.android.testing.CustomTestApplication + +// Hilt will generate an application class that extends TestAppBase +@CustomTestApplication(TestAppBase::class) +interface TestApp diff --git a/project/app/src/androidTest/java/mobi/lab/sample/TestAppBase.kt b/project/app/src/androidTest/java/mobi/lab/sample/TestAppBase.kt new file mode 100644 index 0000000..0c3a5b9 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/TestAppBase.kt @@ -0,0 +1,16 @@ +package mobi.lab.sample + +import android.app.Application +import androidx.test.espresso.IdlingRegistry +import mobi.lab.sample.util.RealIdler +import timber.log.Timber + +open class TestAppBase : Application() { + + override fun onCreate() { + super.onCreate() + // Any other relevant setup we need to make + Timber.plant(Timber.DebugTree()) + IdlingRegistry.getInstance().register(RealIdler.idlingResource) + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/demo/login/LoginActivityTest.kt b/project/app/src/androidTest/java/mobi/lab/sample/demo/login/LoginActivityTest.kt new file mode 100644 index 0000000..88f5a1c --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/demo/login/LoginActivityTest.kt @@ -0,0 +1,124 @@ +package mobi.lab.sample.demo.login + +import androidx.test.espresso.Espresso +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.activityScenarioRule +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import mobi.lab.sample.R +import mobi.lab.sample.common.rx.SchedulerProvider +import mobi.lab.sample.demo.main.MainActivity +import mobi.lab.sample.util.hasNoTextInputLayoutError +import mobi.lab.sample.util.hasTextInputLayoutError +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import javax.inject.Inject + +@HiltAndroidTest +class LoginActivityTest { + + @get:Rule + var hiltRule = HiltAndroidRule(this) + + /** + * Use [androidx.test.ext.junit.rules.ActivityScenarioRule] to create and launch the activity under test before each test, + * and close it after each test. This is a replacement for + * [androidx.test.rule.ActivityTestRule]. + */ + @get:Rule + val activityScenarioRule = activityScenarioRule() + + @Inject + lateinit var schedulers: SchedulerProvider + + private lateinit var activity: LoginActivity + + @Before + fun setup() { + hiltRule.inject() + Intents.init() + activityScenarioRule.scenario.onActivity { + activity = it + } + } + + @After + fun tearDown() { + Intents.release() + } + + @Test + fun show_input_error_when_fields_are_empty_rxidler() { + onView(withId(R.id.button_login)).perform(click()) + + onView(withId(R.id.input_layout_email)).check(matches(hasTextInputLayoutError(TEXT_ID_REQUIRED))) + onView(withId(R.id.input_layout_password)).check(matches(hasTextInputLayoutError(TEXT_ID_REQUIRED))) + + Intents.assertNoUnverifiedIntents() + } + + @Test + fun show_input_error_when_only_username_is_filled_rxidler() { + onView(withId(R.id.edit_text_email)).perform(typeText("asd")) + onView(withId(R.id.button_login)).perform(click()) + + onView(withId(R.id.input_layout_email)).check(matches(hasNoTextInputLayoutError())) + onView(withId(R.id.input_layout_password)).check(matches(hasTextInputLayoutError(TEXT_ID_REQUIRED))) + + Intents.assertNoUnverifiedIntents() + } + + @Test + fun show_input_error_when_only_password_is_filled_rxidler() { + onView(withId(R.id.edit_text_password)).perform(typeText("asd")) + onView(withId(R.id.button_login)).perform(click()) + + onView(withId(R.id.input_layout_email)).check(matches(hasTextInputLayoutError(TEXT_ID_REQUIRED))) + onView(withId(R.id.input_layout_password)).check(matches(hasNoTextInputLayoutError())) + + Intents.assertNoUnverifiedIntents() + } + + @Test + fun login_success_when_fields_are_filled_rxidler() { + onView(withId(R.id.edit_text_email)).perform(typeText("asd")) + onView(withId(R.id.edit_text_password)).perform(typeText("asd")) + onView(withId(R.id.button_login)).perform(click()) + + Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.name)) + } + + @Test + fun show_error_dialog_when_login_fails_rxidler() { + // "test" is a keyword to trigger an error response. See LoginUseCase implementation + onView(withId(R.id.edit_text_email)).perform(typeText("test")) + onView(withId(R.id.edit_text_password)).perform(typeText("asd")) + onView(withId(R.id.button_login)).perform(click()) + + // Validate the dialog and close it + onView(withText(R.string.error_generic)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + Espresso.pressBack() + + onView(withId(R.id.input_layout_email)).check(matches(hasNoTextInputLayoutError())) + onView(withId(R.id.input_layout_password)).check(matches(hasNoTextInputLayoutError())) + + Intents.assertNoUnverifiedIntents() + } + + companion object { + private val TEXT_ID_REQUIRED = R.string.demo_text_required + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/di/SchedulerModuleTest.kt b/project/app/src/androidTest/java/mobi/lab/sample/di/SchedulerModuleTest.kt new file mode 100644 index 0000000..492f022 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/di/SchedulerModuleTest.kt @@ -0,0 +1,38 @@ +package mobi.lab.sample.di + +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import mobi.lab.sample.common.rx.SchedulerProvider +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import javax.inject.Inject +import kotlin.test.assertSame + +/** + * A sample test showing how to inject dependencies via Dagger. + * Verifies that our TestAppComponent gets its schedulers from TestSchedulerModule + */ +@HiltAndroidTest +class SchedulerModuleTest { + + @get:Rule + var hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var schedulers: SchedulerProvider + + @Before + fun setup() { + hiltRule.inject() + } + + @Test + fun verify_test_schedulers() { + assertSame(schedulers.main, AndroidSchedulers.mainThread()) + assertSame(schedulers.io, Schedulers.io()) + assertSame(schedulers.computation, Schedulers.computation()) + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/di/TestIdlerModule.kt b/project/app/src/androidTest/java/mobi/lab/sample/di/TestIdlerModule.kt new file mode 100644 index 0000000..6b5b21d --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/di/TestIdlerModule.kt @@ -0,0 +1,24 @@ +package mobi.lab.sample.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import mobi.lab.sample.app.common.di.IdlerModule +import mobi.lab.sample.app.common.test.Idler +import mobi.lab.sample.util.RealIdler +import javax.inject.Singleton + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [IdlerModule::class] +) +object TestIdlerModule { + + @Singleton + @Provides + fun provideIdler(): Idler { + return RealIdler + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/di/TestSchedulerModule.kt b/project/app/src/androidTest/java/mobi/lab/sample/di/TestSchedulerModule.kt new file mode 100644 index 0000000..62445a2 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/di/TestSchedulerModule.kt @@ -0,0 +1,24 @@ +package mobi.lab.sample.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import mobi.lab.sample.app.common.test.Idler +import mobi.lab.sample.common.rx.SchedulerProvider +import mobi.lab.sample.util.TestSchedulerProvider +import javax.inject.Singleton + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [SchedulerModule::class] +) +object TestSchedulerModule { + + @Singleton + @Provides + fun provideSchedulerProvider(idler: Idler): SchedulerProvider { + return TestSchedulerProvider(idler) + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/util/CustomMatchers.kt b/project/app/src/androidTest/java/mobi/lab/sample/util/CustomMatchers.kt new file mode 100644 index 0000000..cdb3565 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/util/CustomMatchers.kt @@ -0,0 +1,49 @@ +package mobi.lab.sample.util + +import android.view.View +import androidx.annotation.StringRes +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.material.textfield.TextInputLayout +import mobi.lab.sample.app.common.isStringEmpty +import mobi.lab.sample.app.common.stringEquals +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher + +fun hasNoTextInputLayoutError(): Matcher { + return object : TypeSafeMatcher() { + + override fun describeTo(description: Description?) { + description?.appendText("Expected no error text") + } + + override fun matchesSafely(view: View?): Boolean { + if (view !is TextInputLayout) { + return false + } + + return isStringEmpty(view.error) + } + } +} + +fun hasTextInputLayoutError(expectedErrorText: String): Matcher { + return object : TypeSafeMatcher() { + + override fun describeTo(description: Description?) { + description?.appendText("Expected error $expectedErrorText not found") + } + + override fun matchesSafely(view: View?): Boolean { + if (view !is TextInputLayout) { + return false + } + + return stringEquals(expectedErrorText, view.error) + } + } +} + +fun hasTextInputLayoutError(@StringRes id: Int): Matcher { + return hasTextInputLayoutError(InstrumentationRegistry.getInstrumentation().targetContext.getString(id)) +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/util/CustomTestRunner.kt b/project/app/src/androidTest/java/mobi/lab/sample/util/CustomTestRunner.kt new file mode 100644 index 0000000..502c40c --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/util/CustomTestRunner.kt @@ -0,0 +1,18 @@ +package mobi.lab.sample.util + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import mobi.lab.sample.TestApp_Application + +/** + * A runner to provide a different Application class from the regular application code. + * Referenced from app/build.gradle. + */ +@Suppress("unused") +class CustomTestRunner : AndroidJUnitRunner() { + + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, TestApp_Application::class.java.name, context) + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/util/RealIdler.kt b/project/app/src/androidTest/java/mobi/lab/sample/util/RealIdler.kt new file mode 100644 index 0000000..bbf8b9b --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/util/RealIdler.kt @@ -0,0 +1,71 @@ +package mobi.lab.sample.util + +import androidx.test.espresso.idling.CountingIdlingResource +import mobi.lab.sample.app.common.test.Idler +import mobi.lab.sample.app.common.test.IdlerToken +import timber.log.Timber +import java.util.Collections +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +object RealIdler : Idler { + // NB! Do not forget to register this with IdlingRegistry. + val idlingResource: CountingIdlingResource = CountingIdlingResource("RealIdler", true) + + internal val set = Collections.newSetFromMap(ConcurrentHashMap()) + + override fun token(): IdlerToken { + return IdlerToken(newKey()) + } + + override fun busy(): IdlerToken { + return busyInternal(newKey()) + } + + override fun busy(key: Any): IdlerToken { + return busyInternal(key) + } + + override fun busy(token: IdlerToken) { + busyInternal(token.key) + } + + override fun done(key: Any) { + doneInternal(key) + } + + override fun done(token: IdlerToken) { + doneInternal(token.key) + } + + private fun busyInternal(key: Any): IdlerToken { + debugLog("busyInternal key=$key set=$set") + idlingResource.dumpStateToLogs() + + val added = set.add(key) + if (!added) { + throw RuntimeException("Idler is already busy with key $key. Invalid state when marking $key as busy") + } + idlingResource.increment() + return IdlerToken(key) + } + + private fun doneInternal(key: Any) { + debugLog("doneInternal key=$key set=$set") + idlingResource.dumpStateToLogs() + + val removed = set.remove(key) + if (!removed) { + throw RuntimeException("Idler is not busy with key $key. Invalid state when marking $key as done.") + } + idlingResource.decrement() + } + + private fun debugLog(message: String) { + Timber.v(message) + } + + private fun newKey(): Any { + return UUID.randomUUID() + } +} diff --git a/project/app/src/androidTest/java/mobi/lab/sample/util/TestSchedulerProvider.kt b/project/app/src/androidTest/java/mobi/lab/sample/util/TestSchedulerProvider.kt new file mode 100644 index 0000000..c1d31e0 --- /dev/null +++ b/project/app/src/androidTest/java/mobi/lab/sample/util/TestSchedulerProvider.kt @@ -0,0 +1,46 @@ +package mobi.lab.sample.util + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Maybe +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import mobi.lab.sample.app.common.test.Idler +import mobi.lab.sample.common.rx.SchedulerProvider + +class TestSchedulerProvider(private val idler: Idler) : SchedulerProvider() { + + override val main: Scheduler = AndroidSchedulers.mainThread() + override val computation: Scheduler = Schedulers.computation() + override val io: Scheduler = Schedulers.io() + + override fun Observable.transformObservableInternal(): Observable { + val token = idler.token() + return this + .doOnSubscribe { idler.busy(token) } + .doFinally { idler.done(token) } + } + + override fun Single.transformSingleInternal(): Single { + val token = idler.token() + return this + .doOnSubscribe { idler.busy(token) } + .doFinally { idler.done(token) } + } + + override fun Completable.transformCompletableInternal(): Completable { + val token = idler.token() + return this + .doOnSubscribe { idler.busy(token) } + .doFinally { idler.done(token) } + } + + override fun Maybe.transformMaybeInternal(): Maybe { + val token = idler.token() + return this + .doOnSubscribe { idler.busy(token) } + .doFinally { idler.done(token) } + } +} diff --git a/project/app/src/main/java/mobi/lab/sample/di/AppComponent.kt b/project/app/src/main/java/mobi/lab/sample/di/AppComponent.kt deleted file mode 100644 index 19713ae..0000000 --- a/project/app/src/main/java/mobi/lab/sample/di/AppComponent.kt +++ /dev/null @@ -1,31 +0,0 @@ -package mobi.lab.sample.di - -import dagger.Component -import mobi.lab.sample.infrastructure.di.GatewayModule -import mobi.lab.sample.infrastructure.di.MapperModule -import mobi.lab.sample.infrastructure.di.PlatformModule -import mobi.lab.sample.infrastructure.di.ResourceModule -import mobi.lab.sample.infrastructure.di.StorageModule -import javax.inject.Singleton - -/** - * Default Hilt component. - */ -@Singleton -@Component( - modules = [ - ResourceModule::class, - GatewayModule::class, - MapperModule::class, - StorageModule::class, - AppModule::class, - SchedulerModule::class, - PlatformModule::class, - /** - * BuildVariantModule that can be overridden per build variant. No default implementation exists. - * Instead, different build variants (flavours, build types) can provide a different implementation. - */ - BuildVariantModule::class - ] -) -interface AppComponent diff --git a/project/dependencies.gradle b/project/dependencies.gradle index 5e09c80..57af2f6 100644 --- a/project/dependencies.gradle +++ b/project/dependencies.gradle @@ -16,5 +16,23 @@ ext.libsHelper = [ addDependencyInjectionDependencies: { handler -> handler.implementation libs.hilt handler.kapt libs.hilt.compiler - } + }, + + addInstrumentationTestDependencies: { handler -> + handler.androidTestImplementation libs.test.junit + handler.androidTestImplementation libs.test.junit.kotlin + handler.androidTestImplementation libs.test.mockito.core + handler.androidTestImplementation libs.test.mockito.kotlin + + handler.androidTestImplementation libs.test.androidx.junit // Junit runner + handler.androidTestImplementation libs.test.androidx.archtesting + handler.androidTestImplementation libs.test.androidx.testrunner + handler.androidTestImplementation libs.test.androidx.core + handler.androidTestImplementation libs.test.androidx.espresso.core + handler.androidTestImplementation libs.test.androidx.espresso.intents + + handler.androidTestImplementation libs.hilt.android.testing + handler.androidTestAnnotationProcessor libs.hilt.android.compiler + handler.kaptAndroidTest libs.hilt.android.compiler + }, ] diff --git a/project/gradle/libs.versions.toml b/project/gradle/libs.versions.toml index 50036f4..070b9c7 100644 --- a/project/gradle/libs.versions.toml +++ b/project/gradle/libs.versions.toml @@ -70,6 +70,8 @@ rxjava-android = "io.reactivex.rxjava3:rxandroid:3.0.2" # Dagger / Hilt hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } +hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } # Utilities scrolls = "mobi.lab.scrolls:scrolls:2.0.9" @@ -99,3 +101,10 @@ test-junit-kotlin = { module = "org.jetbrains.kotlin:kotlin-test-junit", version test-mockito-core = "org.mockito:mockito-core:5.12.0" test-mockito-kotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" test-androidx-archtesting = "androidx.arch.core:core-testing:2.2.0" + +# UI Testing +test-androidx-testrunner = "androidx.test:runner:1.5.2" +test-androidx-junit = "androidx.test.ext:junit-ktx:1.1.5" +test-androidx-core = "androidx.test:core-ktx:1.5.0" +test-androidx-espresso-core = "androidx.test.espresso:espresso-core:3.5.1" +test-androidx-espresso-intents = "androidx.test.espresso:espresso-intents:3.5.1"