commit
45bcd20b2a
88 changed files with 1082 additions and 549 deletions
27
.github/workflows/release-package.yml
vendored
Normal file
27
.github/workflows/release-package.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: Library Package Publishing
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_USERNAME: "fknives"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-library:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
|
- name: Publish Project
|
||||||
|
run: ./gradlew publishToGitHub
|
||||||
54
README.md
54
README.md
|
|
@ -170,5 +170,59 @@ Open the [shared tests instruction set](./codekata/sharedtests.instructionset.md
|
||||||
In this section we will see how can we share Robolectric test source with AndroidTests to run our same tests on actual device.
|
In this section we will see how can we share Robolectric test source with AndroidTests to run our same tests on actual device.
|
||||||
We will also see how to write AndroidTest End to End Tests.
|
We will also see how to write AndroidTest End to End Tests.
|
||||||
|
|
||||||
|
|
||||||
|
## Util classes
|
||||||
|
|
||||||
|
Additional modules have been added prefixed with test-util.
|
||||||
|
|
||||||
|
These contain all the reusable Test Util classes used in the showcase.
|
||||||
|
|
||||||
|
The Testing setup is extracted into a separate gradle script, which with some modifications, you should be able to easily add to your own project.
|
||||||
|
|
||||||
|
To use the TestUtil classes, you will need to add the GitHub Repository as a source for dependencies:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
// top level build.gradle
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
// ...
|
||||||
|
maven {
|
||||||
|
url "https://maven.pkg.github.com/fknives/AndroidTest-ShowCase"
|
||||||
|
credentials {
|
||||||
|
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
|
||||||
|
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// OR
|
||||||
|
// top level build.gradle.kts
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
// ...
|
||||||
|
maven {
|
||||||
|
url = uri("https://maven.pkg.github.com/fknives/AndroidTest-ShowCase")
|
||||||
|
credentials {
|
||||||
|
username = extra.properties["GITHUB_USERNAME"] as String? ?: System.getenv("GITHUB_USERNAME")
|
||||||
|
password = extra.properties["GITHUB_TOKEN"] as String? ?: System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Latest version:*
|
||||||
|
|
||||||
|
and then you can use the following dependencies:
|
||||||
|
```groovy
|
||||||
|
testImplementation "org.fnives.android.testutil:android-unit-junit5:<latestVersion>" // test-util-junit5-android
|
||||||
|
testImplementation "org.fnives.android.testutil:shared-robolectric:<latestVersion>" // test-util-shared-robolectric
|
||||||
|
testImplementation "org.fnives.android.testutil:android:<latestVersion>" // test-util-android
|
||||||
|
androidTestImplementation "org.fnives.android.testutil:android:<latestVersion>" // test-util-android
|
||||||
|
androidTestImplementation "org.fnives.android.testutil:shared-android:<latestVersion>" // test-util-shared-android
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
[License file](./LICENSE)
|
[License file](./LICENSE)
|
||||||
|
|
|
||||||
|
|
@ -92,55 +92,25 @@ dependencies {
|
||||||
implementation "io.insert-koin:koin-android:$koin_version"
|
implementation "io.insert-koin:koin-android:$koin_version"
|
||||||
implementation "io.insert-koin:koin-androidx-compose:$koin_version"
|
implementation "io.insert-koin:koin-androidx-compose:$koin_version"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$androidx_room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$androidx_room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$androidx_room_version"
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
|
||||||
implementation "io.coil-kt:coil:$coil_version"
|
implementation "io.coil-kt:coil:$coil_version"
|
||||||
implementation "io.coil-kt:coil-compose:$coil_version"
|
implementation "io.coil-kt:coil-compose:$coil_version"
|
||||||
|
|
||||||
implementation project(":core")
|
implementation project(":core")
|
||||||
|
|
||||||
testImplementation "androidx.room:room-testing:$androidx_room_version"
|
applyAppTestDependenciesTo(this)
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
applyComposeTestDependenciesTo(this)
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$testing_junit5_version"
|
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
|
||||||
testImplementation "com.jraska.livedata:testing-ktx:$testing_livedata_version"
|
|
||||||
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
|
||||||
|
|
||||||
// robolectric specific
|
|
||||||
testImplementation "junit:junit:$testing_junit4_version"
|
|
||||||
testImplementation "org.robolectric:robolectric:$testing_robolectric_version"
|
|
||||||
testImplementation "androidx.test:core:$testing_androidx_code_version"
|
|
||||||
testImplementation "androidx.test:runner:$testing_androidx_code_version"
|
|
||||||
testImplementation "androidx.test.ext:junit:$testing_androidx_junit_version"
|
|
||||||
testImplementation "androidx.test.espresso:espresso-core:$testing_espresso_version"
|
|
||||||
testImplementation "androidx.test.espresso:espresso-intents:$testing_espresso_version"
|
|
||||||
testImplementation "androidx.test.espresso:espresso-contrib:$testing_espresso_version"
|
|
||||||
testImplementation project(':mockserver')
|
|
||||||
testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
|
||||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
|
||||||
|
|
||||||
androidTestImplementation "androidx.room:room-testing:$androidx_room_version"
|
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
|
||||||
androidTestImplementation "io.insert-koin:koin-test-junit4:$koin_version"
|
|
||||||
androidTestImplementation "junit:junit:$testing_junit4_version"
|
|
||||||
androidTestImplementation "androidx.test:core:$testing_androidx_code_version"
|
|
||||||
androidTestImplementation "androidx.test:runner:$testing_androidx_code_version"
|
|
||||||
androidTestImplementation "androidx.test.ext:junit:$testing_androidx_junit_version"
|
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:$testing_espresso_version"
|
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$testing_espresso_version"
|
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$testing_espresso_version"
|
|
||||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$androidx_compose_version"
|
|
||||||
testImplementation "androidx.compose.ui:ui-test-junit4:$androidx_compose_version"
|
|
||||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$androidx_compose_version"
|
|
||||||
androidTestImplementation project(':mockserver')
|
androidTestImplementation project(':mockserver')
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version"
|
|
||||||
androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
|
||||||
|
|
||||||
implementation "io.reactivex.rxjava3:rxjava:3.1.4"
|
testImplementation project(':test-util-junit5-android')
|
||||||
|
testImplementation project(':test-util-shared-robolectric')
|
||||||
|
testImplementation project(':test-util-android')
|
||||||
|
androidTestImplementation project(':test-util-android')
|
||||||
|
androidTestImplementation project(':test-util-shared-android')
|
||||||
|
|
||||||
testImplementation testFixtures(project(':core'))
|
testImplementation testFixtures(project(':core'))
|
||||||
androidTestImplementation testFixtures(project(':core'))
|
androidTestImplementation testFixtures(project(':core'))
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ import androidx.test.rule.ActivityTestRule
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.CompositeDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.Disposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.IdlingResourceDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.OkHttp3IdlingResource
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.loopMainThreadFor
|
||||||
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
||||||
import org.fnives.test.showcase.testutils.idling.CompositeDisposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.Disposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.IdlingResourceDisposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.OkHttp3IdlingResource
|
|
||||||
import org.fnives.test.showcase.testutils.idling.loopMainThreadFor
|
|
||||||
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
||||||
import org.fnives.test.showcase.ui.splash.SplashActivity
|
import org.fnives.test.showcase.ui.splash.SplashActivity
|
||||||
import org.hamcrest.Description
|
import org.hamcrest.Description
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import android.app.Instrumentation
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
|
||||||
|
|
||||||
object AndroidMigrationTestRuleFactory : SharedMigrationTestRuleFactory {
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
AndroidMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
AndroidMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass,
|
|
||||||
specs
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>,
|
|
||||||
openFactory: SupportSQLiteOpenHelper.Factory
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
AndroidMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass,
|
|
||||||
specs,
|
|
||||||
openFactory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
object SpecificTestConfigurationsFactory : TestConfigurationsFactory {
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRuleFactory(): SharedMigrationTestRuleFactory =
|
|
||||||
AndroidMigrationTestRuleFactory
|
|
||||||
}
|
|
||||||
|
|
@ -4,13 +4,13 @@ import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.anyResourceIdling
|
||||||
import org.fnives.test.showcase.compose.screen.AppNavigation
|
import org.fnives.test.showcase.compose.screen.AppNavigation
|
||||||
import org.fnives.test.showcase.core.integration.fake.FakeUserDataLocalStorage
|
import org.fnives.test.showcase.core.integration.fake.FakeUserDataLocalStorage
|
||||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.DispatcherTestRule
|
import org.fnives.test.showcase.testutils.idling.DatabaseDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.anyResourceIdling
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -27,7 +27,7 @@ class AuthComposeInstrumentedTest : KoinTest {
|
||||||
|
|
||||||
private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule()
|
private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule()
|
||||||
private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
||||||
private val dispatcherTestRule = DispatcherTestRule()
|
private val dispatcherTestRule = DatabaseDispatcherTestRule()
|
||||||
private lateinit var robot: ComposeLoginRobot
|
private lateinit var robot: ComposeLoginRobot
|
||||||
private lateinit var navigationRobot: ComposeNavigationRobot
|
private lateinit var navigationRobot: ComposeNavigationRobot
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import android.app.Instrumentation
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
|
||||||
|
|
||||||
object RobolectricMigrationTestHelperFactory : SharedMigrationTestRuleFactory {
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
RobolectricMigrationTestHelper(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
RobolectricMigrationTestHelper(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass,
|
|
||||||
specs
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>,
|
|
||||||
openFactory: SupportSQLiteOpenHelper.Factory
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
RobolectricMigrationTestHelper(
|
|
||||||
instrumentation,
|
|
||||||
databaseClass,
|
|
||||||
specs,
|
|
||||||
openFactory
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
object SpecificTestConfigurationsFactory : TestConfigurationsFactory {
|
|
||||||
|
|
||||||
override fun createSharedMigrationTestRuleFactory(): SharedMigrationTestRuleFactory =
|
|
||||||
RobolectricMigrationTestHelperFactory
|
|
||||||
}
|
|
||||||
|
|
@ -11,15 +11,15 @@ import kotlinx.coroutines.test.TestDispatcher
|
||||||
import kotlinx.coroutines.test.resetMain
|
import kotlinx.coroutines.test.resetMain
|
||||||
import kotlinx.coroutines.test.setMain
|
import kotlinx.coroutines.test.setMain
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
import org.fnives.test.showcase.android.testutil.activity.safeClose
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.CompositeDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.Disposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.IdlingResourceDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.OkHttp3IdlingResource
|
||||||
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
||||||
import org.fnives.test.showcase.testutils.idling.CompositeDisposable
|
import org.fnives.test.showcase.testutils.idling.DatabaseDispatcherTestRule.Companion.advanceUntilIdleWithIdlingResources
|
||||||
import org.fnives.test.showcase.testutils.idling.Disposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.IdlingResourceDisposable
|
|
||||||
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule.Companion.advanceUntilIdleWithIdlingResources
|
|
||||||
import org.fnives.test.showcase.testutils.idling.OkHttp3IdlingResource
|
|
||||||
import org.fnives.test.showcase.testutils.safeClose
|
|
||||||
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
||||||
import org.fnives.test.showcase.ui.auth.AuthActivity
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,13 @@ import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
import org.fnives.test.showcase.testutils.configuration.SnackbarVerificationHelper
|
import org.fnives.test.showcase.android.testutil.intent.notIntended
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsNotShown
|
||||||
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsShownWithText
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
import org.hamcrest.core.IsNot.not
|
import org.hamcrest.core.IsNot.not
|
||||||
|
|
||||||
class RobolectricLoginRobot(
|
class RobolectricLoginRobot {
|
||||||
private val snackbarVerificationHelper: SnackbarVerificationHelper = SnackbarVerificationHelper()
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun setUsername(username: String): RobolectricLoginRobot = apply {
|
fun setUsername(username: String): RobolectricLoginRobot = apply {
|
||||||
onView(withId(R.id.user_edit_text))
|
onView(withId(R.id.user_edit_text))
|
||||||
|
|
@ -55,11 +54,11 @@ class RobolectricLoginRobot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsShown(@StringRes stringResID: Int) = apply {
|
fun assertErrorIsShown(@StringRes stringResID: Int) = apply {
|
||||||
snackbarVerificationHelper.assertIsShownWithText(stringResID)
|
assertSnackBarIsShownWithText(stringResID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsNotShown() = apply {
|
fun assertErrorIsNotShown() = apply {
|
||||||
snackbarVerificationHelper.assertIsNotShown()
|
assertSnackBarIsNotShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNavigatedToHome() = apply {
|
fun assertNavigatedToHome() = apply {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
package org.fnives.test.showcase.storage.migration
|
package org.fnives.test.showcase.storage.migration
|
||||||
|
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.fnives.test.showcase.android.testutil.SharedMigrationTestRule
|
||||||
import org.fnives.test.showcase.storage.LocalDatabase
|
import org.fnives.test.showcase.storage.LocalDatabase
|
||||||
import org.fnives.test.showcase.storage.favourite.FavouriteEntity
|
import org.fnives.test.showcase.storage.favourite.FavouriteEntity
|
||||||
import org.fnives.test.showcase.storage.migation.Migration1To2
|
import org.fnives.test.showcase.storage.migation.Migration1To2
|
||||||
import org.fnives.test.showcase.testutils.configuration.SharedMigrationTestRule
|
|
||||||
import org.fnives.test.showcase.testutils.configuration.createSharedMigrationTestRule
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -28,11 +26,7 @@ import java.io.IOException
|
||||||
class MigrationToLatestInstrumentedTest {
|
class MigrationToLatestInstrumentedTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val helper: SharedMigrationTestRule = createSharedMigrationTestRule<LocalDatabase>(
|
val helper = SharedMigrationTestRule<LocalDatabase>(instrumentation = InstrumentationRegistry.getInstrumentation())
|
||||||
InstrumentationRegistry.getInstrumentation(),
|
|
||||||
emptyList(),
|
|
||||||
FrameworkSQLiteOpenHelperFactory()
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getMigratedRoomDatabase(): LocalDatabase {
|
private fun getMigratedRoomDatabase(): LocalDatabase {
|
||||||
val database: LocalDatabase = Room.databaseBuilder(
|
val database: LocalDatabase = Room.databaseBuilder(
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import android.app.Instrumentation
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
interface SharedMigrationTestRule : TestRule {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun createDatabase(name: String, version: Int): SupportSQLiteDatabase
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun runMigrationsAndValidate(
|
|
||||||
name: String,
|
|
||||||
version: Int,
|
|
||||||
validateDroppedTables: Boolean,
|
|
||||||
vararg migrations: Migration
|
|
||||||
): SupportSQLiteDatabase
|
|
||||||
|
|
||||||
fun closeWhenFinished(db: SupportSQLiteDatabase)
|
|
||||||
fun closeWhenFinished(db: RoomDatabase)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified DB : RoomDatabase> createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
SpecificTestConfigurationsFactory.createSharedMigrationTestRuleFactory()
|
|
||||||
.createSharedMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
DB::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
inline fun <reified DB : RoomDatabase> createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
specs: List<AutoMigrationSpec>
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
SpecificTestConfigurationsFactory.createSharedMigrationTestRuleFactory()
|
|
||||||
.createSharedMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
DB::class.java,
|
|
||||||
specs
|
|
||||||
)
|
|
||||||
|
|
||||||
inline fun <reified DB : RoomDatabase> createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
specs: List<AutoMigrationSpec>,
|
|
||||||
openFactory: SupportSQLiteOpenHelper.Factory
|
|
||||||
): SharedMigrationTestRule =
|
|
||||||
SpecificTestConfigurationsFactory.createSharedMigrationTestRuleFactory()
|
|
||||||
.createSharedMigrationTestRule(
|
|
||||||
instrumentation,
|
|
||||||
DB::class.java,
|
|
||||||
specs,
|
|
||||||
openFactory
|
|
||||||
)
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
import android.app.Instrumentation
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
|
||||||
|
|
||||||
interface SharedMigrationTestRuleFactory {
|
|
||||||
|
|
||||||
fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
): SharedMigrationTestRule
|
|
||||||
|
|
||||||
fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>
|
|
||||||
): SharedMigrationTestRule
|
|
||||||
|
|
||||||
fun createSharedMigrationTestRule(
|
|
||||||
instrumentation: Instrumentation,
|
|
||||||
databaseClass: Class<out RoomDatabase>,
|
|
||||||
specs: List<AutoMigrationSpec>,
|
|
||||||
openFactory: SupportSQLiteOpenHelper.Factory
|
|
||||||
): SharedMigrationTestRule
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the platform specific configurations for Robolectric and AndroidTest.
|
|
||||||
*
|
|
||||||
* Each should have an object [SpecificTestConfigurationsFactory] implementing this interface so the SharedTests are
|
|
||||||
* configured properly.
|
|
||||||
*/
|
|
||||||
interface TestConfigurationsFactory {
|
|
||||||
|
|
||||||
fun createSharedMigrationTestRuleFactory(): SharedMigrationTestRuleFactory
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
fun runOnUIAwaitOnCurrent(action: () -> Unit) {
|
|
||||||
if (Looper.myLooper() === Looper.getMainLooper()) {
|
|
||||||
action()
|
|
||||||
} else {
|
|
||||||
val deferred = CompletableDeferred<Unit>()
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
|
||||||
action()
|
|
||||||
deferred.complete(Unit)
|
|
||||||
}
|
|
||||||
runBlocking { deferred.await() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,14 +3,16 @@ package org.fnives.test.showcase.testutils.idling
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.TestDispatcher
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
import org.fnives.test.showcase.testutils.runOnUIAwaitOnCurrent
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.anyResourceIdling
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.awaitIdlingResources
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.runOnUIAwaitOnCurrent
|
||||||
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DispatcherTestRule : TestRule {
|
class DatabaseDispatcherTestRule : TestRule {
|
||||||
|
|
||||||
private lateinit var testDispatcher: TestDispatcher
|
private lateinit var testDispatcher: TestDispatcher
|
||||||
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
|
||||||
|
|
||||||
interface Disposable {
|
|
||||||
val isDisposed: Boolean
|
|
||||||
fun dispose()
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +1,14 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
package org.fnives.test.showcase.testutils.idling
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.TestDispatcher
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
import kotlinx.coroutines.test.resetMain
|
|
||||||
import kotlinx.coroutines.test.setMain
|
|
||||||
import org.fnives.test.showcase.testutils.runOnUIAwaitOnCurrent
|
|
||||||
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
import org.fnives.test.showcase.testutils.storage.TestDatabaseInitialization
|
||||||
import org.junit.rules.TestRule
|
import org.fnives.test.showcase.android.testutil.synchronization.MainDispatcherTestRule as LibMainDispatcherTestRule
|
||||||
import org.junit.runner.Description
|
|
||||||
import org.junit.runners.model.Statement
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MainDispatcherTestRule : TestRule {
|
class MainDispatcherTestRule(useStandard: Boolean = true) : LibMainDispatcherTestRule(useStandard) {
|
||||||
|
|
||||||
private lateinit var testDispatcher: TestDispatcher
|
override fun onTestDispatcherInitialized(testDispatcher: TestDispatcher) {
|
||||||
|
TestDatabaseInitialization.overwriteDatabaseInitialization(testDispatcher)
|
||||||
override fun apply(base: Statement, description: Description): Statement =
|
|
||||||
object : Statement() {
|
|
||||||
@Throws(Throwable::class)
|
|
||||||
override fun evaluate() {
|
|
||||||
val dispatcher = StandardTestDispatcher()
|
|
||||||
Dispatchers.setMain(dispatcher)
|
|
||||||
testDispatcher = dispatcher
|
|
||||||
TestDatabaseInitialization.overwriteDatabaseInitialization(dispatcher)
|
|
||||||
try {
|
|
||||||
base.evaluate()
|
|
||||||
} finally {
|
|
||||||
Dispatchers.resetMain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun advanceUntilIdleWithIdlingResources() = runOnUIAwaitOnCurrent {
|
|
||||||
testDispatcher.advanceUntilIdleWithIdlingResources()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun advanceUntilIdle() = runOnUIAwaitOnCurrent {
|
|
||||||
testDispatcher.scheduler.advanceUntilIdle()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun advanceTimeBy(delayInMillis: Long) = runOnUIAwaitOnCurrent {
|
|
||||||
testDispatcher.scheduler.advanceTimeBy(delayInMillis)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun TestDispatcher.advanceUntilIdleWithIdlingResources() {
|
|
||||||
scheduler.advanceUntilIdle() // advance until a request is sent
|
|
||||||
while (anyResourceIdling()) { // check if any request is in progress
|
|
||||||
awaitIdlingResources() // complete all requests and other idling resources
|
|
||||||
scheduler.advanceUntilIdle() // run coroutines after request is finished
|
|
||||||
}
|
|
||||||
scheduler.advanceUntilIdle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ package org.fnives.test.showcase.testutils.idling
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.CompositeDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.Disposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.IdlingResourceDisposable
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.OkHttp3IdlingResource
|
||||||
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.runner.intent.IntentStubberRegistry
|
import androidx.test.runner.intent.IntentStubberRegistry
|
||||||
|
import org.fnives.test.showcase.android.testutil.activity.safeClose
|
||||||
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.safeClose
|
|
||||||
import org.fnives.test.showcase.ui.auth.AuthActivity
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.fnives.test.showcase.ui.home.HomeRobot
|
import org.fnives.test.showcase.ui.home.HomeRobot
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
|
|
@ -19,7 +19,7 @@ object SetupAuthenticationState : KoinTest {
|
||||||
fun setupLogin(
|
fun setupLogin(
|
||||||
mainDispatcherTestRule: MainDispatcherTestRule,
|
mainDispatcherTestRule: MainDispatcherTestRule,
|
||||||
mockServerScenarioSetup: MockServerScenarioSetup,
|
mockServerScenarioSetup: MockServerScenarioSetup,
|
||||||
resetIntents: Boolean = true
|
resetIntents: Boolean = true,
|
||||||
) {
|
) {
|
||||||
resetIntentsIfNeeded(resetIntents) {
|
resetIntentsIfNeeded(resetIntents) {
|
||||||
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"))
|
mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"))
|
||||||
|
|
@ -40,7 +40,7 @@ object SetupAuthenticationState : KoinTest {
|
||||||
|
|
||||||
fun setupLogout(
|
fun setupLogout(
|
||||||
mainDispatcherTestRule: MainDispatcherTestRule,
|
mainDispatcherTestRule: MainDispatcherTestRule,
|
||||||
resetIntents: Boolean = true
|
resetIntents: Boolean = true,
|
||||||
) {
|
) {
|
||||||
resetIntentsIfNeeded(resetIntents) {
|
resetIntentsIfNeeded(resetIntents) {
|
||||||
val activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
val activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withParent
|
import androidx.test.espresso.matcher.ViewMatchers.withParent
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
import org.fnives.test.showcase.android.testutil.intent.notIntended
|
||||||
|
import org.fnives.test.showcase.android.testutil.viewaction.imageview.WithDrawable
|
||||||
|
import org.fnives.test.showcase.android.testutil.viewaction.swiperefresh.PullToRefresh
|
||||||
import org.fnives.test.showcase.model.content.Content
|
import org.fnives.test.showcase.model.content.Content
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
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.auth.AuthActivity
|
||||||
import org.hamcrest.Matchers.allOf
|
import org.hamcrest.Matchers.allOf
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package org.fnives.test.showcase.ui.home
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.fnives.test.showcase.android.testutil.activity.safeClose
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.loopMainThreadFor
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
import org.fnives.test.showcase.network.mockserver.ContentData
|
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.content.ContentScenario
|
||||||
|
|
@ -10,9 +12,6 @@ import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshToken
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.AsyncDiffUtilInstantTestRule
|
import org.fnives.test.showcase.testutils.idling.AsyncDiffUtilInstantTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.loopMainThreadFor
|
|
||||||
import org.fnives.test.showcase.testutils.idling.loopMainThreadUntilIdleWithIdlingResources
|
|
||||||
import org.fnives.test.showcase.testutils.safeClose
|
|
||||||
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogin
|
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogin
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
|
@ -175,9 +174,6 @@ class MainActivityInstrumentedTest : KoinTest {
|
||||||
|
|
||||||
robot.swipeRefresh()
|
robot.swipeRefresh()
|
||||||
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
||||||
loopMainThreadUntilIdleWithIdlingResources()
|
|
||||||
mainDispatcherTestRule.advanceTimeBy(1000L)
|
|
||||||
loopMainThreadFor(1000)
|
|
||||||
|
|
||||||
robot
|
robot
|
||||||
.assertContainsError()
|
.assertContainsError()
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
import org.fnives.test.showcase.android.testutil.activity.safeClose
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.safeClose
|
|
||||||
import org.fnives.test.showcase.ui.auth.AuthActivity
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,14 @@ import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
import org.fnives.test.showcase.testutils.configuration.SnackbarVerificationHelper
|
import org.fnives.test.showcase.android.testutil.intent.notIntended
|
||||||
import org.fnives.test.showcase.testutils.viewactions.ReplaceProgressBarDrawableToStatic
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsNotShown
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsShownWithText
|
||||||
|
import org.fnives.test.showcase.android.testutil.viewaction.progressbar.ReplaceProgressBarDrawableToStatic
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
import org.hamcrest.core.IsNot.not
|
import org.hamcrest.core.IsNot.not
|
||||||
|
|
||||||
class LoginRobot(
|
class LoginRobot {
|
||||||
private val snackbarVerificationHelper: SnackbarVerificationHelper = SnackbarVerificationHelper()
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun setupIntentResults() {
|
fun setupIntentResults() {
|
||||||
Intents.intending(hasComponent(MainActivity::class.java.canonicalName))
|
Intents.intending(hasComponent(MainActivity::class.java.canonicalName))
|
||||||
|
|
@ -68,7 +67,7 @@ class LoginRobot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsShown(@StringRes stringResID: Int) = apply {
|
fun assertErrorIsShown(@StringRes stringResID: Int) = apply {
|
||||||
snackbarVerificationHelper.assertIsShownWithText(stringResID)
|
assertSnackBarIsShownWithText(stringResID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertLoadingBeforeRequests() = apply {
|
fun assertLoadingBeforeRequests() = apply {
|
||||||
|
|
@ -82,7 +81,7 @@ class LoginRobot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsNotShown() = apply {
|
fun assertErrorIsNotShown() = apply {
|
||||||
snackbarVerificationHelper.assertIsNotShown()
|
assertSnackBarIsNotShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNavigatedToHome() = apply {
|
fun assertNavigatedToHome() = apply {
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@ import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
import org.fnives.test.showcase.testutils.configuration.SnackbarVerificationHelper
|
import org.fnives.test.showcase.android.testutil.intent.notIntended
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsNotShown
|
||||||
|
import org.fnives.test.showcase.android.testutil.snackbar.SnackbarVerificationHelper.assertSnackBarIsShownWithText
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
import org.hamcrest.core.IsNot
|
import org.hamcrest.core.IsNot
|
||||||
|
|
||||||
class CodeKataSharedRobotTest(
|
class CodeKataSharedRobotTest {
|
||||||
private val snackbarVerificationHelper: SnackbarVerificationHelper = SnackbarVerificationHelper()
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun setUsername(username: String): CodeKataSharedRobotTest = apply {
|
fun setUsername(username: String): CodeKataSharedRobotTest = apply {
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.user_edit_text))
|
Espresso.onView(ViewMatchers.withId(R.id.user_edit_text))
|
||||||
|
|
@ -53,11 +52,11 @@ class CodeKataSharedRobotTest(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsShown(@StringRes stringResID: Int): CodeKataSharedRobotTest = apply {
|
fun assertErrorIsShown(@StringRes stringResID: Int): CodeKataSharedRobotTest = apply {
|
||||||
snackbarVerificationHelper.assertIsShownWithText(stringResID)
|
assertSnackBarIsShownWithText(stringResID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertErrorIsNotShown(): CodeKataSharedRobotTest = apply {
|
fun assertErrorIsNotShown(): CodeKataSharedRobotTest = apply {
|
||||||
snackbarVerificationHelper.assertIsNotShown()
|
assertSnackBarIsNotShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertNavigatedToHome(): CodeKataSharedRobotTest = apply {
|
fun assertNavigatedToHome(): CodeKataSharedRobotTest = apply {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.fnives.test.showcase.android.testutil.activity.safeClose
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
||||||
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule
|
||||||
import org.fnives.test.showcase.testutils.safeClose
|
|
||||||
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogin
|
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogin
|
||||||
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogout
|
import org.fnives.test.showcase.testutils.statesetup.SetupAuthenticationState.setupLogout
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import android.app.Instrumentation
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
import org.fnives.test.showcase.testutils.viewactions.notIntended
|
import org.fnives.test.showcase.android.testutil.intent.notIntended
|
||||||
import org.fnives.test.showcase.ui.auth.AuthActivity
|
import org.fnives.test.showcase.ui.auth.AuthActivity
|
||||||
import org.fnives.test.showcase.ui.home.MainActivity
|
import org.fnives.test.showcase.ui.home.MainActivity
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package org.fnives.test.showcase.di
|
package org.fnives.test.showcase.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||||
import org.fnives.test.showcase.model.network.BaseUrl
|
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.auth.AuthViewModel
|
||||||
import org.fnives.test.showcase.ui.home.MainViewModel
|
import org.fnives.test.showcase.ui.home.MainViewModel
|
||||||
import org.fnives.test.showcase.ui.splash.SplashViewModel
|
import org.fnives.test.showcase.ui.splash.SplashViewModel
|
||||||
|
|
@ -20,7 +20,7 @@ import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
@ExtendWith(TestMainDispatcher::class)
|
@ExtendWith(StandardTestMainDispatcher::class)
|
||||||
class DITest : KoinTest {
|
class DITest : KoinTest {
|
||||||
|
|
||||||
private val authViewModel by inject<AuthViewModel>()
|
private val authViewModel by inject<AuthViewModel>()
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package org.fnives.test.showcase.testutils
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.TestDispatcher
|
|
||||||
import kotlinx.coroutines.test.resetMain
|
|
||||||
import kotlinx.coroutines.test.setMain
|
|
||||||
import org.fnives.test.showcase.testutils.TestMainDispatcher.Companion.testDispatcher
|
|
||||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
|
||||||
import org.junit.jupiter.api.extension.BeforeEachCallback
|
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom Junit5 Extension which replaces the main dispatcher with a [TestDispatcher]
|
|
||||||
*
|
|
||||||
* One can access the test dispatcher via [testDispatcher] static getter.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
class TestMainDispatcher : BeforeEachCallback, AfterEachCallback {
|
|
||||||
|
|
||||||
override fun beforeEach(context: ExtensionContext?) {
|
|
||||||
val testDispatcher = StandardTestDispatcher()
|
|
||||||
privateTestDispatcher = testDispatcher
|
|
||||||
Dispatchers.setMain(testDispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterEach(context: ExtensionContext?) {
|
|
||||||
Dispatchers.resetMain()
|
|
||||||
privateTestDispatcher = null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var privateTestDispatcher: TestDispatcher? = null
|
|
||||||
val testDispatcher: TestDispatcher
|
|
||||||
get() = privateTestDispatcher
|
|
||||||
?: throw IllegalStateException("TestMainDispatcher is in afterEach State")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,12 +3,12 @@ package org.fnives.test.showcase.ui.auth
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||||
import org.fnives.test.showcase.model.auth.LoginStatus
|
import org.fnives.test.showcase.model.auth.LoginStatus
|
||||||
import org.fnives.test.showcase.model.shared.Answer
|
import org.fnives.test.showcase.model.shared.Answer
|
||||||
import org.fnives.test.showcase.testutils.InstantExecutorExtension
|
|
||||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
|
||||||
import org.fnives.test.showcase.ui.shared.Event
|
import org.fnives.test.showcase.ui.shared.Event
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
|
@ -27,13 +27,13 @@ import org.mockito.kotlin.whenever
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class AuthViewModelTest {
|
internal class AuthViewModelTest {
|
||||||
|
|
||||||
private lateinit var sut: AuthViewModel
|
private lateinit var sut: AuthViewModel
|
||||||
private lateinit var mockLoginUseCase: LoginUseCase
|
private lateinit var mockLoginUseCase: LoginUseCase
|
||||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
@ -169,7 +169,7 @@ internal class AuthViewModelTest {
|
||||||
@ParameterizedTest(name = "GIVEN answer success loginStatus {0} WHEN login called THEN error {1} is shown")
|
@ParameterizedTest(name = "GIVEN answer success loginStatus {0} WHEN login called THEN error {1} is shown")
|
||||||
fun invalidStatusResultsInErrorState(
|
fun invalidStatusResultsInErrorState(
|
||||||
loginStatus: LoginStatus,
|
loginStatus: LoginStatus,
|
||||||
errorType: AuthViewModel.ErrorType
|
errorType: AuthViewModel.ErrorType,
|
||||||
) {
|
) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Success(loginStatus))
|
whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Success(loginStatus))
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
package org.fnives.test.showcase.ui.auth
|
package org.fnives.test.showcase.ui.auth
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||||
import org.fnives.test.showcase.testutils.InstantExecutorExtension
|
|
||||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
|
|
||||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class CodeKataAuthViewModel {
|
class CodeKataAuthViewModel {
|
||||||
|
|
||||||
private lateinit var sut: AuthViewModel
|
private lateinit var sut: AuthViewModel
|
||||||
private lateinit var mockLoginUseCase: LoginUseCase
|
private lateinit var mockLoginUseCase: LoginUseCase
|
||||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import com.jraska.livedata.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||||
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
|
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
|
||||||
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
||||||
import org.fnives.test.showcase.core.content.GetAllContentUseCase
|
import org.fnives.test.showcase.core.content.GetAllContentUseCase
|
||||||
|
|
@ -14,22 +16,20 @@ import org.fnives.test.showcase.model.content.ContentId
|
||||||
import org.fnives.test.showcase.model.content.FavouriteContent
|
import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
import org.fnives.test.showcase.model.content.ImageUrl
|
import org.fnives.test.showcase.model.content.ImageUrl
|
||||||
import org.fnives.test.showcase.model.shared.Resource
|
import org.fnives.test.showcase.model.shared.Resource
|
||||||
import org.fnives.test.showcase.testutils.InstantExecutorExtension
|
|
||||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.Mockito.verifyNoInteractions
|
||||||
import org.mockito.kotlin.doReturn
|
import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.times
|
import org.mockito.kotlin.times
|
||||||
import org.mockito.kotlin.verify
|
import org.mockito.kotlin.verify
|
||||||
import org.mockito.kotlin.verifyNoInteractions
|
|
||||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class MainViewModelTest {
|
internal class MainViewModelTest {
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ internal class MainViewModelTest {
|
||||||
private lateinit var mockFetchContentUseCase: FetchContentUseCase
|
private lateinit var mockFetchContentUseCase: FetchContentUseCase
|
||||||
private lateinit var mockAddContentToFavouriteUseCase: AddContentToFavouriteUseCase
|
private lateinit var mockAddContentToFavouriteUseCase: AddContentToFavouriteUseCase
|
||||||
private lateinit var mockRemoveContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase
|
private lateinit var mockRemoveContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase
|
||||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ package org.fnives.test.showcase.ui.splash
|
||||||
|
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import org.fnives.test.showcase.android.testutil.InstantExecutorExtension
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher
|
||||||
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||||
import org.fnives.test.showcase.testutils.InstantExecutorExtension
|
|
||||||
import org.fnives.test.showcase.testutils.TestMainDispatcher
|
|
||||||
import org.fnives.test.showcase.ui.shared.Event
|
import org.fnives.test.showcase.ui.shared.Event
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
|
@ -14,13 +14,13 @@ import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class SplashViewModelTest {
|
internal class SplashViewModelTest {
|
||||||
|
|
||||||
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
||||||
private lateinit var sut: SplashViewModel
|
private lateinit var sut: SplashViewModel
|
||||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler
|
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
|
||||||
12
build.gradle
12
build.gradle
|
|
@ -22,6 +22,14 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
maven {
|
||||||
|
url "https://maven.pkg.github.com/fknives/AndroidTest-ShowCase"
|
||||||
|
credentials {
|
||||||
|
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
|
||||||
|
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,4 +41,6 @@ apply from: 'gradlescripts/versions.gradle'
|
||||||
apply from: 'gradlescripts/detekt.config.gradle'
|
apply from: 'gradlescripts/detekt.config.gradle'
|
||||||
apply from: 'gradlescripts/ktlint.gradle'
|
apply from: 'gradlescripts/ktlint.gradle'
|
||||||
apply from: 'gradlescripts/lint.gradle'
|
apply from: 'gradlescripts/lint.gradle'
|
||||||
apply from: 'gradlescripts/testoptions.gradle'
|
apply from: 'gradlescripts/testoptions.gradle'
|
||||||
|
apply from: 'gradlescripts/test.tasks.gradle'
|
||||||
|
apply from: 'gradlescripts/testdependencies.gradle'
|
||||||
|
|
@ -25,12 +25,12 @@ Our test class is `org.fnives.test.showcase.ui.splash.CodeKataSplashViewModelTes
|
||||||
|
|
||||||
To properly test LiveData we need to make them instant, meaning as soon as the value is set the observers are updated. To Do this we can use a `InstantExecutorExtension`.
|
To properly test LiveData we need to make them instant, meaning as soon as the value is set the observers are updated. To Do this we can use a `InstantExecutorExtension`.
|
||||||
|
|
||||||
Also We need to set MainDispatcher as TestDispatcher, for this we can use the `TestMainDispatcher` Extension.
|
Also We need to set MainDispatcher as TestDispatcher, for this we can use the `StandardTestMainDispatcher` Extension.
|
||||||
|
|
||||||
To add this to our TestClass we need to do the following:
|
To add this to our TestClass we need to do the following:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
@ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class)
|
@ExtendWith(InstantExecutorExtension::class, StandardTestMainDispatcher::class)
|
||||||
class CodeKataSplashViewModelTest {
|
class CodeKataSplashViewModelTest {
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ Next let's set up our System Under Test as usual:
|
||||||
```kotlin
|
```kotlin
|
||||||
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
private lateinit var mockIsUserLoggedInUseCase: IsUserLoggedInUseCase
|
||||||
private lateinit var sut: SplashViewModel
|
private lateinit var sut: SplashViewModel
|
||||||
private val testScheduler get() = TestMainDispatcher.testDispatcher.scheduler // just a shortcut
|
private val testScheduler get() = StandardTestMainDispatcher.testDispatcher.scheduler // just a shortcut
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
@ -69,7 +69,7 @@ val navigateToTestObserver = sut.navigateTo.test()
|
||||||
|
|
||||||
Since the action takes place in the ViewModel constructor, instead of additional calls, we need to simulate that time has elapsed.
|
Since the action takes place in the ViewModel constructor, instead of additional calls, we need to simulate that time has elapsed.
|
||||||
|
|
||||||
Note: the `TestMainDispatcher` Extension we are using sets `StandardTestDispatcher` as the dispatcher for `Dispatcher.Main`, that's why our test is linear and not shaky.
|
Note: the `StandardTestMainDispatcher` Extension we are using sets `StandardTestDispatcher` as the dispatcher for `Dispatcher.Main`, that's why our test is linear and not shaky.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
testScheduler.advanceTimeBy(501)
|
testScheduler.advanceTimeBy(501)
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,7 @@ dependencies {
|
||||||
api project(":model")
|
api project(":model")
|
||||||
implementation project(":network")
|
implementation project(":network")
|
||||||
|
|
||||||
testImplementation "io.insert-koin:koin-test-junit5:$koin_version"
|
applyCoreTestDependenciesTo(this)
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
|
||||||
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"
|
|
||||||
testImplementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
|
||||||
testImplementation "app.cash.turbine:turbine:$turbine_version"
|
|
||||||
|
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$testing_junit5_version"
|
|
||||||
|
|
||||||
testImplementation project(':mockserver')
|
testImplementation project(':mockserver')
|
||||||
testFixturesApi testFixtures(project(':network'))
|
testFixturesApi testFixtures(project(':network'))
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=false
|
android.enableJetifier=false
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
android.disableAutomaticComponentCreation=true
|
||||||
|
|
|
||||||
44
gradlescripts/deploy.aar.gradle
Normal file
44
gradlescripts/deploy.aar.gradle
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
apply plugin: "maven-publish"
|
||||||
|
|
||||||
|
def testUtilVersion = "1.0.1"
|
||||||
|
if (!extensions.extraProperties.has("artifactId")) {
|
||||||
|
throw IllegalStateException("ext.artifactId is not set while applying deploy script")
|
||||||
|
}
|
||||||
|
def testUtilGroupId = "org.fnives.android.testutil"
|
||||||
|
def testUtilArtifactId = extensions.extraProperties.get("artifactId")
|
||||||
|
|
||||||
|
task publishToGitHub(dependsOn: "publishMavenAarPublicationToGitHubPackagesRepository") {
|
||||||
|
group = "Publishing"
|
||||||
|
}
|
||||||
|
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
from android.sourceSets.main.java.srcDirs
|
||||||
|
classifier "sources"
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenAar(MavenPublication) {
|
||||||
|
from components.release
|
||||||
|
|
||||||
|
groupId "$testUtilGroupId"
|
||||||
|
println("$testUtilArtifactId")
|
||||||
|
version "$testUtilVersion"
|
||||||
|
artifactId "$testUtilArtifactId"
|
||||||
|
artifact sourcesJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/fknives/AndroidTest-ShowCase")
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GITHUB_USERNAME")
|
||||||
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
gradlescripts/test.tasks.gradle
Normal file
16
gradlescripts/test.tasks.gradle
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
task jvmTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]) {
|
||||||
|
group = 'Tests'
|
||||||
|
description = 'Run all JVM tests'
|
||||||
|
}
|
||||||
|
|
||||||
|
task robolectricTests(type: Exec) {
|
||||||
|
group = 'Tests'
|
||||||
|
description = 'Run all Robolectric tests based on the Instrumented naming convention'
|
||||||
|
// todo is there a better way?
|
||||||
|
commandLine 'sh', './gradlew', 'testDebugUnitTest', '--tests', 'org.fnives.test.*InstrumentedTest'
|
||||||
|
}
|
||||||
|
|
||||||
|
task androidTests(dependsOn: ["app:connectedDebugAndroidTest"]) {
|
||||||
|
group = 'Tests'
|
||||||
|
description = 'Run Android tests'
|
||||||
|
}
|
||||||
148
gradlescripts/testdependencies.gradle
Normal file
148
gradlescripts/testdependencies.gradle
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
project.ext {
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------USAGE------------------
|
||||||
|
add line in root build.gradle:
|
||||||
|
apply from: 'gradlescripts/testdependencies.gradle' (this file)
|
||||||
|
then in your modules you can use:
|
||||||
|
|
||||||
|
------------------NETWORK(Java Module)------------------
|
||||||
|
dependencies {
|
||||||
|
applyNetworkTestDependenciesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
------------------CORE(Java Module)------------------
|
||||||
|
dependencies {
|
||||||
|
applyCoreTestDependenciesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
------------------APP(Android Module)------------------
|
||||||
|
dependencies {
|
||||||
|
applyAppTestDependenciesTo(this)
|
||||||
|
applyComposeTestDependenciesTo(this) // if you are using compose
|
||||||
|
}
|
||||||
|
|
||||||
|
------------------VERSIONS------------------
|
||||||
|
versions try to get the global value, if not found they fall back to some defaults.
|
||||||
|
You can find them just below
|
||||||
|
*/
|
||||||
|
|
||||||
|
def propertyOrNull = { key ->
|
||||||
|
if (extensions.extraProperties.has(key)) {
|
||||||
|
return extensions.extraProperties.get(key)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------VERSIONS------------------
|
||||||
|
def testing_junit5_version = propertyOrNull('junit5_version') ?: "5.7.0"
|
||||||
|
def testing_junit4_version = propertyOrNull('junit4_version') ?: "4.13.2"
|
||||||
|
def testing_robolectric_version = propertyOrNull('robolectric_version') ?: "4.7"
|
||||||
|
def testing_androidx_code_version = propertyOrNull('androidx_test_version') ?: "1.4.0"
|
||||||
|
def testing_androidx_junit_version = propertyOrNull('androidx_junit_version') ?: "1.1.3"
|
||||||
|
def testing_espresso_version = propertyOrNull('espresso_version') ?: "3.4.0"
|
||||||
|
def testing_androidx_arch_core_version = propertyOrNull('arch_core_version') ?: "2.1.0"
|
||||||
|
def test_coroutines_version = propertyOrNull('coroutines_version') ?: "1.6.0"
|
||||||
|
def testing_kotlin_mockito_version = propertyOrNull('mockito_version') ?: "4.0.0"
|
||||||
|
def testing_koin_version = propertyOrNull('koin_version') ?: "3.1.2"
|
||||||
|
def testing_json_assert_version = propertyOrNull('json_assert_version') ?: "1.5.0"
|
||||||
|
def testing_okhttp3 = propertyOrNull('okhttp_version') ?: "4.9.3"
|
||||||
|
def testing_turbine_version = propertyOrNull('turbine_version') ?: "0.7.0"
|
||||||
|
def testing_androidx_room_version = propertyOrNull('room_version') ?: "2.4.2"
|
||||||
|
def testing_livedata_version = propertyOrNull('testing_livedata_version') ?: "1.2.0"
|
||||||
|
def testing_compose_version = propertyOrNull('androidx_compose_version') ?: "1.1.0"
|
||||||
|
def testing_hamcrest_version = propertyOrNull('hamcrest_version') ?: "2.2"
|
||||||
|
|
||||||
|
// ------------------PRIVATE------------------
|
||||||
|
// JUni4 + Espresso + Room
|
||||||
|
def androidSpecificTestDependencies = [
|
||||||
|
"junit:junit:$testing_junit4_version",
|
||||||
|
|
||||||
|
"androidx.room:room-testing:$testing_androidx_room_version",
|
||||||
|
|
||||||
|
"com.jraska.livedata:testing-ktx:$testing_livedata_version",
|
||||||
|
|
||||||
|
"androidx.test:core:$testing_androidx_code_version",
|
||||||
|
"androidx.test:runner:$testing_androidx_code_version",
|
||||||
|
|
||||||
|
"androidx.test.ext:junit:$testing_androidx_junit_version",
|
||||||
|
|
||||||
|
"androidx.test.espresso:espresso-core:$testing_espresso_version",
|
||||||
|
"androidx.test.espresso:espresso-intents:$testing_espresso_version",
|
||||||
|
"androidx.test.espresso:espresso-contrib:$testing_espresso_version",
|
||||||
|
"org.hamcrest:hamcrest:$testing_hamcrest_version",
|
||||||
|
|
||||||
|
"androidx.arch.core:core-testing:$testing_androidx_arch_core_version",
|
||||||
|
]
|
||||||
|
|
||||||
|
// ------------------PRIVATE------------------
|
||||||
|
def applyStandardTestDependenciesTo = { module ->
|
||||||
|
module.dependencies {
|
||||||
|
// coroutine testing
|
||||||
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$test_coroutines_version"
|
||||||
|
|
||||||
|
// mockito, mocking library
|
||||||
|
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
||||||
|
|
||||||
|
testImplementation "io.insert-koin:koin-test-junit5:$testing_koin_version"
|
||||||
|
// junit5
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-params:$testing_junit5_version"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------NETWORK------------------
|
||||||
|
applyNetworkTestDependenciesTo = { module ->
|
||||||
|
applyStandardTestDependenciesTo(module)
|
||||||
|
|
||||||
|
module.dependencies {
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
|
// JSON Assert
|
||||||
|
testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version"
|
||||||
|
|
||||||
|
// mockwebserver + https support
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver:$testing_okhttp3"
|
||||||
|
testImplementation "com.squareup.okhttp3:okhttp-tls:$testing_okhttp3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------CORE------------------
|
||||||
|
applyCoreTestDependenciesTo = { module ->
|
||||||
|
applyStandardTestDependenciesTo(module)
|
||||||
|
|
||||||
|
module.dependencies {
|
||||||
|
// turbine, flow testing
|
||||||
|
testImplementation "app.cash.turbine:turbine:$testing_turbine_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------APP------------------
|
||||||
|
applyAppTestDependenciesTo = { module ->
|
||||||
|
applyStandardTestDependenciesTo(module)
|
||||||
|
|
||||||
|
module.dependencies {
|
||||||
|
testImplementation "org.robolectric:robolectric:$testing_robolectric_version"
|
||||||
|
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
||||||
|
|
||||||
|
androidSpecificTestDependencies.forEach { dependency ->
|
||||||
|
testImplementation dependency
|
||||||
|
androidTestImplementation dependency
|
||||||
|
}
|
||||||
|
|
||||||
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$test_coroutines_version"
|
||||||
|
androidTestImplementation "io.insert-koin:koin-test-junit5:$testing_koin_version"
|
||||||
|
androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------COMPOSE------------------
|
||||||
|
applyComposeTestDependenciesTo = { module ->
|
||||||
|
module.dependencies {
|
||||||
|
testImplementation "androidx.compose.ui:ui-test-junit4:$testing_compose_version"
|
||||||
|
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$testing_compose_version"
|
||||||
|
debugImplementation "androidx.compose.ui:ui-test-manifest:$testing_compose_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,21 +62,4 @@ subprojects { module ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
task jvmTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]) {
|
|
||||||
group = 'Tests'
|
|
||||||
description = 'Run all JVM tests'
|
|
||||||
}
|
|
||||||
|
|
||||||
task robolectricTests(type: Exec) {
|
|
||||||
group = 'Tests'
|
|
||||||
description = 'Run all Robolectric tests based on the Instrumented naming convention'
|
|
||||||
// todo is there a better way?
|
|
||||||
commandLine 'sh', './gradlew', 'testDebugUnitTest', '--tests', 'org.fnives.test.*InstrumentedTest'
|
|
||||||
}
|
|
||||||
|
|
||||||
task androidTests(dependsOn: ["app:connectedDebugAndroidTest"]) {
|
|
||||||
group = 'Tests'
|
|
||||||
description = 'Run Android tests'
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
project.ext {
|
project.ext {
|
||||||
androidx_core_version = "1.7.0"
|
androidx_core_version = "1.8.0"
|
||||||
androidx_appcompat_version = "1.4.1"
|
androidx_appcompat_version = "1.4.1"
|
||||||
androidx_material_version = "1.5.0"
|
androidx_material_version = "1.6.1"
|
||||||
androidx_constraintlayout_version = "2.1.3"
|
androidx_constraintlayout_version = "2.1.3"
|
||||||
androidx_livedata_version = "2.4.0"
|
androidx_livedata_version = "2.4.0"
|
||||||
androidx_swiperefreshlayout_version = "1.1.0"
|
androidx_swiperefreshlayout_version = "1.1.0"
|
||||||
androidx_room_version = "2.4.1"
|
room_version = "2.4.2"
|
||||||
activity_ktx_version = "1.4.0"
|
activity_ktx_version = "1.4.0"
|
||||||
androidx_navigation = "2.4.0"
|
androidx_navigation = "2.4.0"
|
||||||
|
|
||||||
|
|
@ -14,21 +14,23 @@ project.ext {
|
||||||
androidx_compose_constraintlayout_version = "1.0.0"
|
androidx_compose_constraintlayout_version = "1.0.0"
|
||||||
|
|
||||||
coroutines_version = "1.6.0"
|
coroutines_version = "1.6.0"
|
||||||
turbine_version = "0.7.0"
|
|
||||||
koin_version = "3.1.2"
|
koin_version = "3.1.2"
|
||||||
coil_version = "1.4.0"
|
coil_version = "1.4.0"
|
||||||
retrofit_version = "2.9.0"
|
retrofit_version = "2.9.0"
|
||||||
okhttp_version = "4.9.3"
|
okhttp_version = "4.9.3"
|
||||||
moshi_version = "1.13.0"
|
moshi_version = "1.13.0"
|
||||||
|
|
||||||
testing_androidx_code_version = "1.4.0"
|
// testing versions
|
||||||
testing_androidx_junit_version = "1.1.3"
|
turbine_version = "0.7.0"
|
||||||
testing_androidx_arch_core_version = "2.1.0"
|
androidx_test_version = "1.4.0"
|
||||||
|
androidx_junit_version = "1.1.3"
|
||||||
|
arch_core_version = "2.1.0"
|
||||||
testing_livedata_version = "1.2.0"
|
testing_livedata_version = "1.2.0"
|
||||||
testing_kotlin_mockito_version = "4.0.0"
|
mockito_version = "4.0.0"
|
||||||
testing_junit5_version = "5.7.0"
|
junit5_version = "5.7.0"
|
||||||
testing_json_assert_version = "1.5.0"
|
json_assert_version = "1.5.0"
|
||||||
testing_junit4_version = "4.13.2"
|
junit4_version = "4.13.2"
|
||||||
testing_robolectric_version = "4.7"
|
robolectric_version = "4.7"
|
||||||
testing_espresso_version = "3.4.0"
|
espresso_version = "3.4.0"
|
||||||
|
hamcrest_version = "2.2"
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ dependencies {
|
||||||
api "com.squareup.okhttp3:mockwebserver:$okhttp_version"
|
api "com.squareup.okhttp3:mockwebserver:$okhttp_version"
|
||||||
api "com.squareup.okhttp3:okhttp-tls:$okhttp_version"
|
api "com.squareup.okhttp3:okhttp-tls:$okhttp_version"
|
||||||
|
|
||||||
implementation "org.skyscreamer:jsonassert:$testing_json_assert_version"
|
implementation "org.skyscreamer:jsonassert:$json_assert_version"
|
||||||
implementation "junit:junit:$testing_junit4_version"
|
implementation "junit:junit:$junit4_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -24,12 +24,9 @@ dependencies {
|
||||||
|
|
||||||
api project(":model")
|
api project(":model")
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
applyNetworkTestDependenciesTo(this)
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
|
||||||
testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version"
|
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
|
||||||
|
|
||||||
testFixturesApi project(':mockserver')
|
testFixturesApi project(':mockserver')
|
||||||
testFixturesApi "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
testFixturesApi "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
|
||||||
testFixturesApi "io.insert-koin:koin-test-junit5:$koin_version"
|
testFixturesApi "io.insert-koin:koin-test-junit5:$koin_version"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
rootProject.name = "TestShowCase"
|
||||||
include ':mockserver'
|
include ':mockserver'
|
||||||
include ':model'
|
include ':model'
|
||||||
include ':core'
|
include ':core'
|
||||||
include ':network'
|
include ':network'
|
||||||
include ':app'
|
include ':app'
|
||||||
rootProject.name = "TestShowCase"
|
include ':test-util-shared-android'
|
||||||
|
include ':test-util-shared-robolectric'
|
||||||
|
include ':test-util-android'
|
||||||
|
include ':test-util-junit5-android'
|
||||||
|
|
|
||||||
1
test-util-android/.gitignore
vendored
Normal file
1
test-util-android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
56
test-util-android/build.gradle
Normal file
56
test-util-android/build.gradle
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 31
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 21
|
||||||
|
targetSdk 31
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = false
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LGPL2.1'
|
||||||
|
exclude 'META-INF/AL2.0'
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||||
|
|
||||||
|
implementation "androidx.test:core:$androidx_test_version"
|
||||||
|
implementation "androidx.test.espresso:espresso-core:$espresso_version"
|
||||||
|
implementation "androidx.test.espresso:espresso-intents:$espresso_version"
|
||||||
|
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
|
||||||
|
implementation "com.google.android.material:material:$androidx_material_version"
|
||||||
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
|
||||||
|
implementation "androidx.core:core-ktx:$androidx_core_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.artifactId = "android"
|
||||||
|
apply from: "../gradlescripts/deploy.aar.gradle"
|
||||||
0
test-util-android/consumer-rules.pro
Normal file
0
test-util-android/consumer-rules.pro
Normal file
21
test-util-android/proguard-rules.pro
vendored
Normal file
21
test-util-android/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
5
test-util-android/src/main/AndroidManifest.xml
Normal file
5
test-util-android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.fnives.test.showcase.android.testutil">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
package org.fnives.test.showcase.testutils
|
package org.fnives.test.showcase.android.testutil.activity
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround for issue: https://github.com/android/android-test/issues/676.
|
||||||
|
*
|
||||||
|
* Call this instead of ActivityScenario.close().
|
||||||
|
*/
|
||||||
fun <T : Activity> ActivityScenario<T>.safeClose() {
|
fun <T : Activity> ActivityScenario<T>.safeClose() {
|
||||||
workaroundForActivityScenarioCLoseLockingUp()
|
workaroundForActivityScenarioCLoseLockingUp()
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should not be needed, we shouldn't use sleep ever.
|
* This should not be needed, we shouldn't use sleep basically ever.
|
||||||
* However, it seems to be and issue described here: https://github.com/android/android-test/issues/676
|
* However, it seems to be and issue described here: https://github.com/android/android-test/issues/676
|
||||||
*
|
*
|
||||||
* If an activity is finished in code, the ActivityScenario.close() can hang 30 to 45 seconds.
|
* If an activity is finished in code, the ActivityScenario.close() can hang 30 to 45 seconds.
|
||||||
* This sleeps let's the Activity finish it state change and unlocks the ActivityScenario.
|
* This sleep let's the Activity finish it's state change and unlocks the ActivityScenario.
|
||||||
*
|
*
|
||||||
* As soon as that issue is closed, this should be removed as well.
|
* As soon as that issue is closed, this should be removed as well.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package org.fnives.test.showcase.testutils.viewactions
|
package org.fnives.test.showcase.android.testutil.intent
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
import org.hamcrest.StringDescription
|
import org.hamcrest.StringDescription
|
||||||
|
|
||||||
|
@Suppress("SwallowedException")
|
||||||
fun notIntended(matcher: Matcher<Intent>) {
|
fun notIntended(matcher: Matcher<Intent>) {
|
||||||
try {
|
try {
|
||||||
intended(matcher)
|
intended(matcher)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
package org.fnives.test.showcase.android.testutil.snackbar
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso
|
||||||
|
|
@ -8,22 +9,25 @@ import androidx.test.espresso.ViewAction
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.assertion.ViewAssertions
|
import androidx.test.espresso.assertion.ViewAssertions
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import com.google.android.material.R
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
|
import com.google.android.material.R as MaterialR
|
||||||
|
|
||||||
class SnackbarVerificationHelper {
|
object SnackbarVerificationHelper {
|
||||||
|
|
||||||
fun assertIsShownWithText(@StringRes stringResID: Int) {
|
@SuppressLint("RestrictedApi")
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.snackbar_text))
|
fun assertSnackBarIsShownWithText(@StringRes stringResID: Int, doDismiss: Boolean = true) {
|
||||||
|
Espresso.onView(ViewMatchers.withId(MaterialR.id.snackbar_text))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.withText(stringResID)))
|
.check(ViewAssertions.matches(ViewMatchers.withText(stringResID)))
|
||||||
Espresso.onView(ViewMatchers.isAssignableFrom(Snackbar.SnackbarLayout::class.java)).perform(ViewActions.swipeRight())
|
if (doDismiss) {
|
||||||
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainUntilSnackbarDismissed())
|
Espresso.onView(ViewMatchers.isAssignableFrom(Snackbar.SnackbarLayout::class.java)).perform(ViewActions.swipeRight())
|
||||||
|
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainUntilSnackbarDismissed())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertIsNotShown() {
|
fun assertSnackBarIsNotShown() {
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.snackbar_text)).check(ViewAssertions.doesNotExist())
|
Espresso.onView(ViewMatchers.withId(MaterialR.id.snackbar_text)).check(ViewAssertions.doesNotExist())
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoopMainUntilSnackbarDismissed : ViewAction {
|
class LoopMainUntilSnackbarDismissed : ViewAction {
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil.synchronization
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.anyResourceIdling
|
||||||
|
import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.awaitIdlingResources
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
open class MainDispatcherTestRule(private val useStandard: Boolean = true) : TestRule {
|
||||||
|
|
||||||
|
private lateinit var testDispatcher: TestDispatcher
|
||||||
|
|
||||||
|
override fun apply(base: Statement, description: Description): Statement =
|
||||||
|
object : Statement() {
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
override fun evaluate() {
|
||||||
|
val dispatcher = if (useStandard) StandardTestDispatcher() else UnconfinedTestDispatcher()
|
||||||
|
Dispatchers.setMain(dispatcher)
|
||||||
|
testDispatcher = dispatcher
|
||||||
|
onTestDispatcherInitialized(testDispatcher)
|
||||||
|
try {
|
||||||
|
base.evaluate()
|
||||||
|
} finally {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
onTestDispatcherReset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onTestDispatcherInitialized(testDispatcher: TestDispatcher) = Unit
|
||||||
|
|
||||||
|
open fun onTestDispatcherReset() = Unit
|
||||||
|
|
||||||
|
fun advanceUntilIdleWithIdlingResources() = runOnUIAwaitOnCurrent {
|
||||||
|
testDispatcher.advanceUntilIdleWithIdlingResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun advanceUntilIdle() = runOnUIAwaitOnCurrent {
|
||||||
|
testDispatcher.scheduler.advanceUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun advanceTimeBy(delayInMillis: Long) = runOnUIAwaitOnCurrent {
|
||||||
|
testDispatcher.scheduler.advanceTimeBy(delayInMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun TestDispatcher.advanceUntilIdleWithIdlingResources() {
|
||||||
|
scheduler.advanceUntilIdle() // advance until a request is sent
|
||||||
|
while (anyResourceIdling()) { // check if any request is in progress
|
||||||
|
awaitIdlingResources() // complete all requests and other idling resources
|
||||||
|
scheduler.advanceUntilIdle() // run coroutines after request is finished
|
||||||
|
}
|
||||||
|
scheduler.advanceUntilIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
package org.fnives.test.showcase.android.testutil.synchronization.idlingresources
|
||||||
|
|
||||||
class CompositeDisposable(disposable: List<Disposable> = emptyList()) : Disposable {
|
class CompositeDisposable(disposable: List<Disposable> = emptyList()) : Disposable {
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil.synchronization.idlingresources
|
||||||
|
|
||||||
|
interface Disposable {
|
||||||
|
val isDisposed: Boolean
|
||||||
|
fun dispose()
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
package org.fnives.test.showcase.android.testutil.synchronization.idlingresources
|
||||||
|
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
|
|
||||||
internal class IdlingResourceDisposable(private val idlingResource: IdlingResource) : Disposable {
|
class IdlingResourceDisposable(private val idlingResource: IdlingResource) : Disposable {
|
||||||
override var isDisposed: Boolean = false
|
override var isDisposed: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
package org.fnives.test.showcase.android.testutil.synchronization.idlingresources
|
||||||
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.test.espresso.Espresso
|
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import org.fnives.test.showcase.android.testutil.synchronization.loopMainThreadFor
|
||||||
import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadFor
|
|
||||||
import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadUntilIdle
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
// workaround, issue with idlingResources is tracked here https://github.com/robolectric/robolectric/issues/4807
|
// workaround, issue with idlingResources is tracked here https://github.com/robolectric/robolectric/issues/4807
|
||||||
|
|
@ -41,20 +37,3 @@ private fun IdlingResource.awaitUntilIdle() {
|
||||||
Thread.sleep(100L)
|
Thread.sleep(100L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loopMainThreadUntilIdleWithIdlingResources() {
|
|
||||||
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainThreadUntilIdle()) // advance until a request is sent
|
|
||||||
while (anyResourceIdling()) { // check if any request is in progress
|
|
||||||
awaitIdlingResources() // complete all requests and other idling resources
|
|
||||||
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainThreadUntilIdle()) // run coroutines after request is finished
|
|
||||||
}
|
|
||||||
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainThreadUntilIdle())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loopMainThreadFor(delay: Long) {
|
|
||||||
if (Looper.getMainLooper().isCurrentThread) {
|
|
||||||
Thread.sleep(200L)
|
|
||||||
} else {
|
|
||||||
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainThreadFor(delay))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.idling
|
package org.fnives.test.showcase.android.testutil.synchronization.idlingresources
|
||||||
|
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
|
@ -40,7 +40,7 @@ class OkHttp3IdlingResource private constructor(
|
||||||
* this instance using `Espresso.registerIdlingResources`.
|
* this instance using `Espresso.registerIdlingResources`.
|
||||||
*/
|
*/
|
||||||
@CheckResult
|
@CheckResult
|
||||||
@NonNull // Extra guards as a library.
|
@NonNull
|
||||||
fun create(@NonNull name: String?, @NonNull client: OkHttpClient?): OkHttp3IdlingResource {
|
fun create(@NonNull name: String?, @NonNull client: OkHttpClient?): OkHttp3IdlingResource {
|
||||||
if (name == null) throw NullPointerException("name == null")
|
if (name == null) throw NullPointerException("name == null")
|
||||||
if (client == null) throw NullPointerException("client == null")
|
if (client == null) throw NullPointerException("client == null")
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil.synchronization
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.fnives.test.showcase.android.testutil.viewaction.LoopMainThreadFor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given action on the MainThread and blocks currentThread, until it is completed.
|
||||||
|
*
|
||||||
|
* It is safe to call this from the MainThread.
|
||||||
|
*/
|
||||||
|
fun runOnUIAwaitOnCurrent(action: () -> Unit) {
|
||||||
|
if (Looper.myLooper() === Looper.getMainLooper()) {
|
||||||
|
action()
|
||||||
|
} else {
|
||||||
|
val deferred = CompletableDeferred<Unit>()
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
action()
|
||||||
|
deferred.complete(Unit)
|
||||||
|
}
|
||||||
|
runBlocking { deferred.await() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loopMainThreadFor(delay: Long) {
|
||||||
|
if (Looper.getMainLooper().thread == Thread.currentThread()) {
|
||||||
|
Thread.sleep(200L)
|
||||||
|
} else {
|
||||||
|
Espresso.onView(ViewMatchers.isRoot()).perform(LoopMainThreadFor(delay))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.viewactions
|
package org.fnives.test.showcase.android.testutil.viewaction
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.test.espresso.UiController
|
import androidx.test.espresso.UiController
|
||||||
|
|
@ -15,13 +15,3 @@ class LoopMainThreadFor(private val delayInMillis: Long) : ViewAction {
|
||||||
uiController.loopMainThreadForAtLeast(delayInMillis)
|
uiController.loopMainThreadForAtLeast(delayInMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoopMainThreadUntilIdle : ViewAction {
|
|
||||||
override fun getConstraints(): Matcher<View> = Matchers.isA(View::class.java)
|
|
||||||
|
|
||||||
override fun getDescription(): String = "loop MainThread for until Idle"
|
|
||||||
|
|
||||||
override fun perform(uiController: UiController, view: View?) {
|
|
||||||
uiController.loopMainThreadUntilIdle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil.viewaction
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
|
||||||
|
class LoopMainThreadUntilIdle : ViewAction {
|
||||||
|
override fun getConstraints(): Matcher<View> = Matchers.isA(View::class.java)
|
||||||
|
|
||||||
|
override fun getDescription(): String = "loop MainThread for until Idle"
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController, view: View?) {
|
||||||
|
uiController.loopMainThreadUntilIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.viewactions
|
package org.fnives.test.showcase.android.testutil.viewaction.imageview
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
|
|
@ -26,7 +26,7 @@ class WithDrawable(
|
||||||
override fun matchesSafely(view: View): Boolean {
|
override fun matchesSafely(view: View): Boolean {
|
||||||
val context = view.context
|
val context = view.context
|
||||||
val tintColor = tint?.let { ContextCompat.getColor(view.context, it) }
|
val tintColor = tint?.let { ContextCompat.getColor(view.context, it) }
|
||||||
val expectedBitmap = context.getDrawable(id)?.apply {
|
val expectedBitmap = ContextCompat.getDrawable(context, id)?.apply {
|
||||||
if (tintColor != null) {
|
if (tintColor != null) {
|
||||||
setTintList(ColorStateList.valueOf(tintColor))
|
setTintList(ColorStateList.valueOf(tintColor))
|
||||||
setTintMode(tintMode)
|
setTintMode(tintMode)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.viewactions
|
package org.fnives.test.showcase.android.testutil.viewaction.progressbar
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package org.fnives.test.showcase.testutils.viewactions
|
package org.fnives.test.showcase.android.testutil.viewaction.swiperefresh
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import androidx.swiperefreshlayout.widget.listener
|
import androidx.swiperefreshlayout.widget.listener
|
||||||
import androidx.test.espresso.UiController
|
import androidx.test.espresso.UiController
|
||||||
import androidx.test.espresso.ViewAction
|
import androidx.test.espresso.ViewAction
|
||||||
import org.fnives.test.showcase.testutils.runOnUIAwaitOnCurrent
|
import org.fnives.test.showcase.android.testutil.synchronization.runOnUIAwaitOnCurrent
|
||||||
import org.hamcrest.BaseMatcher
|
import org.hamcrest.BaseMatcher
|
||||||
import org.hamcrest.CoreMatchers.isA
|
import org.hamcrest.CoreMatchers.isA
|
||||||
import org.hamcrest.Description
|
import org.hamcrest.Description
|
||||||
1
test-util-junit5-android/.gitignore
vendored
Normal file
1
test-util-junit5-android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
49
test-util-junit5-android/build.gradle
Normal file
49
test-util-junit5-android/build.gradle
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 31
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 21
|
||||||
|
targetSdk 31
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = false
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LGPL2.1'
|
||||||
|
exclude 'META-INF/AL2.0'
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
|
||||||
|
implementation "androidx.arch.core:core-runtime:$arch_core_version"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.artifactId = "android-unit-junit5"
|
||||||
|
apply from: "../gradlescripts/deploy.aar.gradle"
|
||||||
0
test-util-junit5-android/consumer-rules.pro
Normal file
0
test-util-junit5-android/consumer-rules.pro
Normal file
21
test-util-junit5-android/proguard-rules.pro
vendored
Normal file
21
test-util-junit5-android/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
5
test-util-junit5-android/src/main/AndroidManifest.xml
Normal file
5
test-util-junit5-android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.fnives.test.showcase.android.testutil">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package org.fnives.test.showcase.testutils
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.arch.core.executor.ArchTaskExecutor
|
import androidx.arch.core.executor.ArchTaskExecutor
|
||||||
import androidx.arch.core.executor.TaskExecutor
|
import androidx.arch.core.executor.TaskExecutor
|
||||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||||
|
|
@ -11,9 +12,11 @@ import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
*
|
*
|
||||||
* reference: https://developer.android.com/reference/androidx/arch/core/executor/testing/InstantTaskExecutorRule
|
* reference: https://developer.android.com/reference/androidx/arch/core/executor/testing/InstantTaskExecutorRule
|
||||||
*
|
*
|
||||||
* A JUnit5 Extensions that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.
|
* A JUnit5 Extensions that swaps the background executor used by the Architecture Components with a different
|
||||||
|
* one which executes each task synchronously.
|
||||||
* You can use this extension for your host side tests that use Architecture Components.
|
* You can use this extension for your host side tests that use Architecture Components.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
|
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
|
||||||
|
|
||||||
override fun beforeEach(context: ExtensionContext?) {
|
override fun beforeEach(context: ExtensionContext?) {
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
|
import org.fnives.test.showcase.android.testutil.StandardTestMainDispatcher.Companion.testDispatcher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Junit5 Extension which replaces the main dispatcher with a Standard [TestDispatcher]
|
||||||
|
*
|
||||||
|
* One can access the test dispatcher via [testDispatcher] static getter.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class StandardTestMainDispatcher : TestMainDispatcher() {
|
||||||
|
|
||||||
|
override var createdTestDispatcher: TestDispatcher?
|
||||||
|
get() = privateTestDispatcher
|
||||||
|
set(value) {
|
||||||
|
privateTestDispatcher = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createDispatcher(): TestDispatcher = StandardTestDispatcher()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var privateTestDispatcher: TestDispatcher? = null
|
||||||
|
val testDispatcher: TestDispatcher
|
||||||
|
get() = privateTestDispatcher
|
||||||
|
?: throw IllegalStateException("StandardTestMainDispatcher is in afterEach State")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Junit5 Extension which replaces the main dispatcher with a [TestDispatcher]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
abstract class TestMainDispatcher : BeforeEachCallback, AfterEachCallback {
|
||||||
|
|
||||||
|
protected abstract var createdTestDispatcher: TestDispatcher?
|
||||||
|
|
||||||
|
abstract fun createDispatcher(): TestDispatcher
|
||||||
|
|
||||||
|
final override fun beforeEach(context: ExtensionContext?) {
|
||||||
|
val testDispatcher = createDispatcher()
|
||||||
|
createdTestDispatcher = testDispatcher
|
||||||
|
Dispatchers.setMain(testDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun afterEach(context: ExtensionContext?) {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
createdTestDispatcher = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import org.fnives.test.showcase.android.testutil.UnconfinedTestMainDispatcher.Companion.testDispatcher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Junit5 Extension which replaces the main dispatcher with a Unconfined [TestDispatcher]
|
||||||
|
*
|
||||||
|
* One can access the test dispatcher via [testDispatcher] static getter.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class UnconfinedTestMainDispatcher : TestMainDispatcher() {
|
||||||
|
|
||||||
|
override var createdTestDispatcher: TestDispatcher?
|
||||||
|
get() = privateTestDispatcher
|
||||||
|
set(value) {
|
||||||
|
privateTestDispatcher = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createDispatcher(): TestDispatcher = UnconfinedTestDispatcher()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var privateTestDispatcher: TestDispatcher? = null
|
||||||
|
val testDispatcher: TestDispatcher
|
||||||
|
get() = privateTestDispatcher
|
||||||
|
?: throw IllegalStateException("StandardTestMainDispatcher is in afterEach State")
|
||||||
|
}
|
||||||
|
}
|
||||||
1
test-util-shared-android/.gitignore
vendored
Normal file
1
test-util-shared-android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
47
test-util-shared-android/build.gradle
Normal file
47
test-util-shared-android/build.gradle
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 31
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 21
|
||||||
|
targetSdk 31
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = false
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LGPL2.1'
|
||||||
|
exclude 'META-INF/AL2.0'
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.room:room-testing:$room_version"
|
||||||
|
api project(':test-util-shared-robolectric')
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.artifactId = "shared-android"
|
||||||
|
apply from: "../gradlescripts/deploy.aar.gradle"
|
||||||
0
test-util-shared-android/consumer-rules.pro
Normal file
0
test-util-shared-android/consumer-rules.pro
Normal file
21
test-util-shared-android/proguard-rules.pro
vendored
Normal file
21
test-util-shared-android/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
5
test-util-shared-android/src/main/AndroidManifest.xml
Normal file
5
test-util-shared-android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.fnives.test.showcase.android.testutil">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
import android.app.Instrumentation
|
import android.app.Instrumentation
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
|
@ -10,6 +10,9 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around [MigrationTestHelper] so it can be created in SharedTests, in both Robolectric and on Device.
|
||||||
|
*/
|
||||||
class AndroidMigrationTestRule : SharedMigrationTestRule {
|
class AndroidMigrationTestRule : SharedMigrationTestRule {
|
||||||
|
|
||||||
private val migrationTestHelper: MigrationTestHelper
|
private val migrationTestHelper: MigrationTestHelper
|
||||||
|
|
@ -35,8 +38,7 @@ class AndroidMigrationTestRule : SharedMigrationTestRule {
|
||||||
specs: List<AutoMigrationSpec>,
|
specs: List<AutoMigrationSpec>,
|
||||||
openFactory: SupportSQLiteOpenHelper.Factory
|
openFactory: SupportSQLiteOpenHelper.Factory
|
||||||
) {
|
) {
|
||||||
migrationTestHelper =
|
migrationTestHelper = MigrationTestHelper(instrumentation, databaseClass, specs, openFactory)
|
||||||
MigrationTestHelper(instrumentation, databaseClass, specs, openFactory)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun apply(base: Statement, description: Description): Statement =
|
override fun apply(base: Statement, description: Description): Statement =
|
||||||
1
test-util-shared-robolectric/.gitignore
vendored
Normal file
1
test-util-shared-robolectric/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
47
test-util-shared-robolectric/build.gradle
Normal file
47
test-util-shared-robolectric/build.gradle
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 31
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 21
|
||||||
|
targetSdk 31
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = false
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LGPL2.1'
|
||||||
|
exclude 'META-INF/AL2.0'
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.room:room-testing:$room_version"
|
||||||
|
implementation "androidx.arch.core:core-testing:$arch_core_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.artifactId = "shared-robolectric"
|
||||||
|
apply from: "../gradlescripts/deploy.aar.gradle"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.fnives.test.showcase.android.testutil.robolectric">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unifying API above [MigrationTestHelper][androidx.room.testing.MigrationTestHelper].
|
||||||
|
*
|
||||||
|
* This is intended to be used in MigrationTests that are shared. Meaning the same test can run on both Device and via Robolectric.
|
||||||
|
*/
|
||||||
|
interface SharedMigrationTestRule : TestRule {
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun createDatabase(name: String, version: Int): SupportSQLiteDatabase
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun runMigrationsAndValidate(
|
||||||
|
name: String,
|
||||||
|
version: Int,
|
||||||
|
validateDroppedTables: Boolean,
|
||||||
|
vararg migrations: Migration
|
||||||
|
): SupportSQLiteDatabase
|
||||||
|
|
||||||
|
fun closeWhenFinished(db: SupportSQLiteDatabase)
|
||||||
|
fun closeWhenFinished(db: RoomDatabase)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.fnives.test.showcase.android.testutil
|
||||||
|
|
||||||
|
import android.app.Instrumentation
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.AutoMigrationSpec
|
||||||
|
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||||
|
import org.fnives.test.showcase.android.testutil.robolectric.RobolectricMigrationTestRule
|
||||||
|
|
||||||
|
inline fun <reified Database : RoomDatabase> SharedMigrationTestRule(
|
||||||
|
instrumentation: Instrumentation,
|
||||||
|
): SharedMigrationTestRule =
|
||||||
|
createAndroidClassOrRobolectric(
|
||||||
|
androidClassFactory = { androidClass ->
|
||||||
|
val constructor = androidClass.getConstructor(
|
||||||
|
Instrumentation::class.java,
|
||||||
|
Class::class.java
|
||||||
|
)
|
||||||
|
constructor.newInstance(instrumentation, Database::class.java) as SharedMigrationTestRule
|
||||||
|
},
|
||||||
|
robolectricFactory = {
|
||||||
|
RobolectricMigrationTestRule(instrumentation, Database::class.java)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified Database : RoomDatabase> SharedMigrationTestRule(
|
||||||
|
instrumentation: Instrumentation,
|
||||||
|
specs: List<AutoMigrationSpec>,
|
||||||
|
): SharedMigrationTestRule =
|
||||||
|
createAndroidClassOrRobolectric(
|
||||||
|
androidClassFactory = { androidClass ->
|
||||||
|
val constructor = androidClass.getConstructor(
|
||||||
|
Instrumentation::class.java,
|
||||||
|
Class::class.java,
|
||||||
|
List::class.java
|
||||||
|
)
|
||||||
|
constructor.newInstance(instrumentation, Database::class.java, specs) as SharedMigrationTestRule
|
||||||
|
},
|
||||||
|
robolectricFactory = {
|
||||||
|
RobolectricMigrationTestRule(instrumentation, Database::class.java, specs)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified Database : RoomDatabase> SharedMigrationTestRule(
|
||||||
|
instrumentation: Instrumentation,
|
||||||
|
specs: List<AutoMigrationSpec>,
|
||||||
|
openFactory: SupportSQLiteOpenHelper.Factory,
|
||||||
|
): SharedMigrationTestRule =
|
||||||
|
createAndroidClassOrRobolectric(
|
||||||
|
androidClassFactory = { androidClass ->
|
||||||
|
val constructor = androidClass.getConstructor(
|
||||||
|
Instrumentation::class.java,
|
||||||
|
Class::class.java,
|
||||||
|
List::class.java,
|
||||||
|
SupportSQLiteOpenHelper.Factory::class.java
|
||||||
|
)
|
||||||
|
constructor.newInstance(instrumentation, Database::class.java, specs, openFactory) as SharedMigrationTestRule
|
||||||
|
},
|
||||||
|
robolectricFactory = {
|
||||||
|
RobolectricMigrationTestRule(instrumentation, Database::class.java, specs, openFactory)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fun createAndroidClassOrRobolectric(
|
||||||
|
androidClassFactory: (Class<*>) -> Any,
|
||||||
|
robolectricFactory: () -> SharedMigrationTestRule,
|
||||||
|
): SharedMigrationTestRule {
|
||||||
|
val androidClass = getAndroidClass()
|
||||||
|
return if (androidClass == null) {
|
||||||
|
robolectricFactory()
|
||||||
|
} else {
|
||||||
|
androidClassFactory(androidClass) as SharedMigrationTestRule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SwallowedException")
|
||||||
|
private fun getAndroidClass() = try {
|
||||||
|
Class.forName("org.fnives.test.showcase.android.testutil.AndroidMigrationTestRule")
|
||||||
|
} catch (classNotFoundException: ClassNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.fnives.test.showcase.testutils.configuration;
|
package org.fnives.test.showcase.android.testutil.robolectric;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
|
|
@ -32,6 +32,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
|
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||||
|
|
||||||
|
import org.fnives.test.showcase.android.testutil.SharedMigrationTestRule;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.junit.rules.TestWatcher;
|
import org.junit.rules.TestWatcher;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
|
|
@ -56,8 +57,9 @@ import java.util.Set;
|
||||||
*
|
*
|
||||||
* reference: https://github.com/robolectric/robolectric/issues/2065
|
* reference: https://github.com/robolectric/robolectric/issues/2065
|
||||||
*/
|
*/
|
||||||
public class RobolectricMigrationTestHelper extends TestWatcher implements SharedMigrationTestRule {
|
@SuppressLint("RestrictedApi")
|
||||||
private static final String TAG = "RobolectricMigrationTestHelper";
|
public class RobolectricMigrationTestRule extends TestWatcher implements SharedMigrationTestRule {
|
||||||
|
private static final String TAG = "RobolectricMigrationTR";
|
||||||
private final String mAssetsFolder;
|
private final String mAssetsFolder;
|
||||||
private final SupportSQLiteOpenHelper.Factory mOpenFactory;
|
private final SupportSQLiteOpenHelper.Factory mOpenFactory;
|
||||||
private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
|
private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
|
||||||
|
|
@ -77,7 +79,7 @@ public class RobolectricMigrationTestHelper extends TestWatcher implements Share
|
||||||
* @param instrumentation The instrumentation instance.
|
* @param instrumentation The instrumentation instance.
|
||||||
* @param databaseClass The Database class to be tested.
|
* @param databaseClass The Database class to be tested.
|
||||||
*/
|
*/
|
||||||
public RobolectricMigrationTestHelper(@NonNull Instrumentation instrumentation,
|
public RobolectricMigrationTestRule(@NonNull Instrumentation instrumentation,
|
||||||
@NonNull Class<? extends RoomDatabase> databaseClass) {
|
@NonNull Class<? extends RoomDatabase> databaseClass) {
|
||||||
this(instrumentation, databaseClass, new ArrayList<>(),
|
this(instrumentation, databaseClass, new ArrayList<>(),
|
||||||
new FrameworkSQLiteOpenHelperFactory());
|
new FrameworkSQLiteOpenHelperFactory());
|
||||||
|
|
@ -96,7 +98,7 @@ public class RobolectricMigrationTestHelper extends TestWatcher implements Share
|
||||||
* @param specs The list of available auto migration specs that will be provided to
|
* @param specs The list of available auto migration specs that will be provided to
|
||||||
* Room at runtime.
|
* Room at runtime.
|
||||||
*/
|
*/
|
||||||
public RobolectricMigrationTestHelper(@NonNull Instrumentation instrumentation,
|
public RobolectricMigrationTestRule(@NonNull Instrumentation instrumentation,
|
||||||
@NonNull Class<? extends RoomDatabase> databaseClass,
|
@NonNull Class<? extends RoomDatabase> databaseClass,
|
||||||
@NonNull List<AutoMigrationSpec> specs) {
|
@NonNull List<AutoMigrationSpec> specs) {
|
||||||
this(instrumentation, databaseClass, specs, new FrameworkSQLiteOpenHelperFactory());
|
this(instrumentation, databaseClass, specs, new FrameworkSQLiteOpenHelperFactory());
|
||||||
|
|
@ -116,7 +118,7 @@ public class RobolectricMigrationTestHelper extends TestWatcher implements Share
|
||||||
* Room at runtime.
|
* Room at runtime.
|
||||||
* @param openFactory Factory class that allows creation of {@link SupportSQLiteOpenHelper}
|
* @param openFactory Factory class that allows creation of {@link SupportSQLiteOpenHelper}
|
||||||
*/
|
*/
|
||||||
public RobolectricMigrationTestHelper(@NonNull Instrumentation instrumentation,
|
public RobolectricMigrationTestRule(@NonNull Instrumentation instrumentation,
|
||||||
@NonNull Class<? extends RoomDatabase> databaseClass,
|
@NonNull Class<? extends RoomDatabase> databaseClass,
|
||||||
@NonNull List<AutoMigrationSpec> specs,
|
@NonNull List<AutoMigrationSpec> specs,
|
||||||
@NonNull SupportSQLiteOpenHelper.Factory openFactory
|
@NonNull SupportSQLiteOpenHelper.Factory openFactory
|
||||||
Loading…
Add table
Add a link
Reference in a new issue