Merge pull request #124 from fknives/issue#106-update-shared-tests
Issue#106 update shared tests
This commit is contained in:
commit
b6b661b055
51 changed files with 330 additions and 49 deletions
1
app-shared-test/.gitignore
vendored
Normal file
1
app-shared-test/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
49
app-shared-test/build.gradle
Normal file
49
app-shared-test/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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
assets.srcDirs += files("$projectDir/../app/schemas".toString())
|
||||||
|
resources.srcDirs += files("$projectDir/../app/schemas".toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since it itself contains the Test it doesn't have tests of it's own
|
||||||
|
disableTestTasks(this)
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(":app")
|
||||||
|
implementation project(':test-util-android')
|
||||||
|
implementation testFixtures(project(':core'))
|
||||||
|
implementation "io.insert-koin:koin-android:$koin_version"
|
||||||
|
implementation project(':test-util-shared-robolectric')
|
||||||
|
|
||||||
|
applyAppSharedTestDependenciesTo(this)
|
||||||
|
}
|
||||||
0
app-shared-test/consumer-rules.pro
Normal file
0
app-shared-test/consumer-rules.pro
Normal file
21
app-shared-test/proguard-rules.pro
vendored
Normal file
21
app-shared-test/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
app-shared-test/src/main/AndroidManifest.xml
Normal file
5
app-shared-test/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.test.shared">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -23,7 +23,7 @@ import java.io.IOException
|
||||||
* https://developer.android.com/training/data-storage/room/migrating-db-versions
|
* https://developer.android.com/training/data-storage/room/migrating-db-versions
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class MigrationToLatestInstrumentedTest {
|
open class MigrationToLatestInstrumentedSharedTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val helper = SharedMigrationTestRule<LocalDatabase>(instrumentation = InstrumentationRegistry.getInstrumentation())
|
val helper = SharedMigrationTestRule<LocalDatabase>(instrumentation = InstrumentationRegistry.getInstrumentation())
|
||||||
|
|
@ -48,7 +48,7 @@ class MigrationToLatestInstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun migrate1To2() {
|
open fun migrate1To2() {
|
||||||
val expectedEntities = setOf(
|
val expectedEntities = setOf(
|
||||||
FavouriteEntity("123"),
|
FavouriteEntity("123"),
|
||||||
FavouriteEntity("124"),
|
FavouriteEntity("124"),
|
||||||
|
|
@ -26,7 +26,7 @@ import org.koin.test.KoinTest
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class MainActivityInstrumentedTest : KoinTest {
|
open class MainActivityInstrumentedSharedTest : KoinTest {
|
||||||
|
|
||||||
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ import org.koin.test.KoinTest
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AuthActivityInstrumentedTest : KoinTest {
|
open class AuthActivityInstrumentedSharedTest : KoinTest {
|
||||||
|
|
||||||
private lateinit var activityScenario: ActivityScenario<AuthActivity>
|
private lateinit var activityScenario: ActivityScenario<AuthActivity>
|
||||||
|
|
||||||
|
|
@ -13,7 +13,8 @@ import org.koin.test.KoinTest
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Ignore("CodeKata")
|
@Ignore("CodeKata")
|
||||||
class CodeKataAuthActivitySharedTest : KoinTest {
|
@Suppress("EmptyFunctionBlock")
|
||||||
|
open class CodeKataAuthActivitySharedTest : KoinTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
|
|
@ -3,7 +3,6 @@ package org.fnives.test.showcase.ui.splash
|
||||||
import androidx.lifecycle.Lifecycle
|
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 org.fnives.test.showcase.android.testutil.activity.SafeCloseActivityRule
|
import org.fnives.test.showcase.android.testutil.activity.SafeCloseActivityRule
|
||||||
import org.fnives.test.showcase.android.testutil.intent.DismissSystemDialogsRule
|
import org.fnives.test.showcase.android.testutil.intent.DismissSystemDialogsRule
|
||||||
import org.fnives.test.showcase.android.testutil.screenshot.ScreenshotRule
|
import org.fnives.test.showcase.android.testutil.screenshot.ScreenshotRule
|
||||||
|
|
@ -16,12 +15,10 @@ import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.RuleChain
|
import org.junit.rules.RuleChain
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
@RunWith(AndroidJUnit4::class)
|
open class SplashActivityInstrumentedSharedTest : KoinTest {
|
||||||
class SplashActivityInstrumentedTest : KoinTest {
|
|
||||||
|
|
||||||
private lateinit var activityScenario: ActivityScenario<SplashActivity>
|
private lateinit var activityScenario: ActivityScenario<SplashActivity>
|
||||||
|
|
||||||
|
|
@ -42,13 +42,11 @@ android {
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
androidTest {
|
androidTest {
|
||||||
java.srcDirs += "src/sharedTest/java"
|
// assets.srcDirs += files("$projectDir/schemas".toString())
|
||||||
assets.srcDirs += files("$projectDir/schemas".toString())
|
|
||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
java.srcDirs += "src/sharedTest/java"
|
|
||||||
java.srcDirs += "src/robolectricTest/java"
|
java.srcDirs += "src/robolectricTest/java"
|
||||||
resources.srcDirs += files("$projectDir/schemas".toString())
|
// resources.srcDirs += files("$projectDir/schemas".toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,6 +113,9 @@ dependencies {
|
||||||
testImplementation testFixtures(project(':core'))
|
testImplementation testFixtures(project(':core'))
|
||||||
androidTestImplementation testFixtures(project(':core'))
|
androidTestImplementation testFixtures(project(':core'))
|
||||||
|
|
||||||
|
testImplementation project(':app-shared-test')
|
||||||
|
androidTestImplementation project(':app-shared-test')
|
||||||
|
|
||||||
// case specific
|
// case specific
|
||||||
implementation project(":examplecase:example-navcontroller")
|
implementation project(":examplecase:example-navcontroller")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.storage.migration
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MigrationToLatestInstrumentedTest : MigrationToLatestInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.home
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MainActivityInstrumentedTest : MainActivityInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.login
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AuthActivityInstrumentedTest : AuthActivityInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.fnives.test.showcase.ui.login.codekata
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Ignore("CodeKata")
|
||||||
|
class CodeKataAuthActivityTest : CodeKataAuthActivitySharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.splash
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SplashActivityInstrumentedTest : SplashActivityInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.storage.migration
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MigrationToLatestInstrumentedTest : MigrationToLatestInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.home
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MainActivityInstrumentedTest : MainActivityInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.login
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AuthActivityInstrumentedTest : AuthActivityInstrumentedSharedTest()
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.fnives.test.showcase.ui.login.codekata
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Ignore("CodeKata")
|
||||||
|
class CodeKataAuthActivityTest : CodeKataAuthActivitySharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.ui.splash
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SplashActivityInstrumentedTest : SplashActivityInstrumentedSharedTest()
|
||||||
|
|
@ -45,4 +45,5 @@ 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/test.tasks.gradle'
|
||||||
apply from: 'gradlescripts/testdependencies.gradle'
|
apply from: 'gradlescripts/testdependencies.gradle'
|
||||||
|
apply from: 'gradlescripts/disable.test.task.gradle'
|
||||||
|
|
@ -13,7 +13,12 @@ In this testing instruction set you will learn how to write simple tests running
|
||||||
|
|
||||||
## Login UI Test
|
## Login UI Test
|
||||||
Instead of writing new tests from scratch, we will modify our existing Robolectric tests so they can be run on a Real Android device as well.N
|
Instead of writing new tests from scratch, we will modify our existing Robolectric tests so they can be run on a Real Android device as well.N
|
||||||
For this we already have a `sharedTest` package.
|
For this we already have a ~~`sharedTest` package~~ a separate app-shared-test module.
|
||||||
|
|
||||||
|
> Sharing sourceSets between unitTest and androidTest is no longer supported in Android Studio.
|
||||||
|
> You may find a bunch of artiles referencing that way of sharing or even on Robolectric sites, but these are now outdated.
|
||||||
|
> We are using the recommendation to that issue which is a shared-test module which depends on :app and it's tests depend on the module. testImplementation project(:shared-test) androidTestImplementation project(:shared-test).
|
||||||
|
> This may seem circular at first, but it's not: shared-test depends on app's main, while app's Tests depend on shared-test.
|
||||||
|
|
||||||
Our classes will be `CodeKataAuthActivitySharedTest` and `CodeKataSharedRobotTest`.
|
Our classes will be `CodeKataAuthActivitySharedTest` and `CodeKataSharedRobotTest`.
|
||||||
|
|
||||||
|
|
@ -34,8 +39,8 @@ Let's open `org.fnives.test.showcase.ui.login.codekata.CodeKataAuthActivityShare
|
||||||
We can see it's identical as our original `org.fnives.test.showcase.ui.codekata.CodeKataAuthActivityInstrumentedTest`.
|
We can see it's identical as our original `org.fnives.test.showcase.ui.codekata.CodeKataAuthActivityInstrumentedTest`.
|
||||||
So let's copy our existing code from the Robolectric test here. For that we can use the body of `org.fnives.test.showcase.ui.RobolectricAuthActivityInstrumentedTest`.
|
So let's copy our existing code from the Robolectric test here. For that we can use the body of `org.fnives.test.showcase.ui.RobolectricAuthActivityInstrumentedTest`.
|
||||||
|
|
||||||
You immediately notice that there are no import issues. That's because sharedTest package is added to the test sources. You may check out the `app/build.gradle` to see how that's done.
|
Of course keep the `open` and the `CodeKataAuthActivitySharedTest` class name and package.
|
||||||
However we need to modify our robot:
|
We need to modify our robot:
|
||||||
```kotlin
|
```kotlin
|
||||||
// Instead of this:
|
// Instead of this:
|
||||||
private lateinit var robot: RobolectricLoginRobot
|
private lateinit var robot: RobolectricLoginRobot
|
||||||
|
|
@ -51,11 +56,25 @@ robot = CodeKataSharedRobotTest()
|
||||||
|
|
||||||
For our starting point, this is all the setup we need. What we now will do is modify this piece of class, so it not only runs via Robolectric, but it can run on Real Devices as well.
|
For our starting point, this is all the setup we need. What we now will do is modify this piece of class, so it not only runs via Robolectric, but it can run on Real Devices as well.
|
||||||
|
|
||||||
|
Now, go to the classes that extend this and remove the `@Ignore("CodeKata")` annotation: `CodeKataAuthActivityTest` in both `unitTest` and `androidTest`.
|
||||||
|
|
||||||
|
> This child classes run when testing so we are sharing a base class, but the child classess will run every Test of the Base class.
|
||||||
|
|
||||||
### 1. Threads
|
### 1. Threads
|
||||||
|
|
||||||
So to discover the differences, let's handle them one by one, by Running our Test.
|
So to discover the differences, let's handle them one by one, by Running our Test.
|
||||||
In shared tests, at least for me, it defaults to Android Test when running the class. So make sure your device is connected, and run the `invalidCredentialsGivenShowsProperErrorMessage` Test. It should start on your device and shall crash.
|
Open `CodeKataAuthActivityTest` inside `androidTest` and overwrite `invalidCredentialsGivenShowsProperErrorMessage`:
|
||||||
You will see something similar:
|
```kotlin
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class CodeKataAuthActivityTest : CodeKataAuthActivitySharedTest() {
|
||||||
|
// if invalidCredentialsGivenShowsProperErrorMessage is not open in base, open it
|
||||||
|
override fun invalidCredentialsGivenShowsProperErrorMessage() {
|
||||||
|
super.invalidCredentialsGivenShowsProperErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Make sure your device (tested on API=30) is connected, and run the `invalidCredentialsGivenShowsProperErrorMessage` Test. It should start on your device and shall crash.
|
||||||
|
You will see something similar in logcat:
|
||||||
```kotlin
|
```kotlin
|
||||||
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
|
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
|
||||||
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
|
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
|
||||||
|
|
@ -66,7 +85,7 @@ So that brings us to the first difference: *while Robolectric uses the same thre
|
||||||
|
|
||||||
So the issue is with this line: `testDispatcher.advanceUntilIdleWithIdlingResources()`. Since we are in the InstrumentedTest's thread, all our coroutines will run there as well, which doesn't play well with LiveData.
|
So the issue is with this line: `testDispatcher.advanceUntilIdleWithIdlingResources()`. Since we are in the InstrumentedTest's thread, all our coroutines will run there as well, which doesn't play well with LiveData.
|
||||||
One idea would be to use LiveData `ArchTaskExecutor.getInstance()` and ensure our LiveData doesn't care about the Thread they are set from, **but** then we would touch our Views from Non-Main Thread, which is still an issue.
|
One idea would be to use LiveData `ArchTaskExecutor.getInstance()` and ensure our LiveData doesn't care about the Thread they are set from, **but** then we would touch our Views from Non-Main Thread, which is still an issue.
|
||||||
**So Instead** What we need to do is run our coroutines on the actual mainThread. We have a handy `runOnUIAwaitOnCurrent` function for that, so let's use it in our `invalidCredentialsGivenShowsProperErrorMessage` test, wrap around our dispatcher call.
|
**So Instead** What we need to do is run our coroutines on the actual mainThread. We have a handy `runOnUIAwaitOnCurrent` function for that, so let's use it in our `invalidCredentialsGivenShowsProperErrorMessage` (inside the base class) test, wrap around our dispatcher call.
|
||||||
|
|
||||||
The full function now will look like this:
|
The full function now will look like this:
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|
@ -458,7 +477,7 @@ To resolve this fast, a possible way is like this:
|
||||||
Another issue can be that Crashlytics or similar services is enabled in your tests. This can be resolved by the same principle as the HiltTestApplication issue, aka custom `AndroidJunitRunner`. Your custom TestClass will initialize only what it needs to.
|
Another issue can be that Crashlytics or similar services is enabled in your tests. This can be resolved by the same principle as the HiltTestApplication issue, aka custom `AndroidJunitRunner`. Your custom TestClass will initialize only what it needs to.
|
||||||
|
|
||||||
#### 4. Dialogs
|
#### 4. Dialogs
|
||||||
Dialogs cannot be tested properly via Robolectric without usage of Shadows, but they can be on Real Device. So what I usually do is setup a function which does one thing in one sourceset while does something else in another. You can see such example like `SpecificTestConfigurationsFactory`. To ease the usage I usually put a function in the sharedTest which uses the object `SpecificTestConfigurationsFactory`.
|
Dialogs cannot be tested properly via Robolectric without usage of Shadows, but they can be on Real Device. So what I usually do is setup a function which does one thing in one sourceset while does something else in another. So either you will have to test them only on real device, or you can create helper module which in AndroidTest uses actual Espresso calls, while if Robolectric is active it uses the Shadow. See how `SharedMigrationTestRule` is setup and extended.
|
||||||
|
|
||||||
#### 5. Resource Access
|
#### 5. Resource Access
|
||||||
Accessing test Resource files can also be an issue, you might not able to access your same test/res folder in AndroidTests. A way to do this is to declare the same folder as androidTest/assets in build gradle and similar to dialogs, create a function which uses Assets in Android Tests and uses Resources in Robolectric tests.
|
Accessing test Resource files can also be an issue, you might not able to access your same test/res folder in AndroidTests. A way to do this is to declare the same folder as androidTest/assets in build gradle and similar to dialogs, create a function which uses Assets in Android Tests and uses Resources in Robolectric tests.
|
||||||
|
|
|
||||||
1
examplecase/example-navcontroller-shared-test/.gitignore
vendored
Normal file
1
examplecase/example-navcontroller-shared-test/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
47
examplecase/example-navcontroller-shared-test/build.gradle
Normal file
47
examplecase/example-navcontroller-shared-test/build.gradle
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'androidx.navigation.safeargs.kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
ignore 'FragmentGradleConfiguration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since it itself contains the Test it doesn't have tests of it's own
|
||||||
|
disableTestTasks(this)
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
applyAppSharedTestDependenciesTo(this)
|
||||||
|
implementation project(":examplecase:example-navcontroller")
|
||||||
|
//noinspection FragmentGradleConfiguration
|
||||||
|
implementation "androidx.fragment:fragment-testing:1.5.3"
|
||||||
|
implementation "androidx.navigation:navigation-testing:$navigation_version"
|
||||||
|
implementation project(':test-util-android')
|
||||||
|
}
|
||||||
21
examplecase/example-navcontroller-shared-test/proguard-rules.pro
vendored
Normal file
21
examplecase/example-navcontroller-shared-test/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
|
||||||
|
|
@ -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.examplecase.navcontroller.shared.test">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -28,7 +28,7 @@ import org.junit.runner.RunWith
|
||||||
* For more info check out https://developer.android.com/guide/navigation/navigation-testing
|
* For more info check out https://developer.android.com/guide/navigation/navigation-testing
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class HomeNavigationTest {
|
open class HomeNavigationSharedTest {
|
||||||
|
|
||||||
private lateinit var fragmentScenario: FragmentScenario<HomeFragment>
|
private lateinit var fragmentScenario: FragmentScenario<HomeFragment>
|
||||||
private lateinit var testNavController: TestNavHostController
|
private lateinit var testNavController: TestNavHostController
|
||||||
|
|
@ -80,7 +80,8 @@ class HomeNavigationTest {
|
||||||
.perform(ViewActions.click())
|
.perform(ViewActions.click())
|
||||||
|
|
||||||
Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
|
Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
|
||||||
Assert.assertEquals(listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment), testNavController.backStack.map { it.destination.id })
|
val expectedBackstack = listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment)
|
||||||
|
Assert.assertEquals(expectedBackstack, testNavController.backStack.map { it.destination.id })
|
||||||
testNavController.backStack.map { it.arguments }
|
testNavController.backStack.map { it.arguments }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +96,8 @@ class HomeNavigationTest {
|
||||||
Espresso.onView(itemViewMatcher(position2)).perform(ViewActions.click())
|
Espresso.onView(itemViewMatcher(position2)).perform(ViewActions.click())
|
||||||
|
|
||||||
Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
|
Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
|
||||||
Assert.assertEquals(listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment), testNavController.backStack.map { it.destination.id })
|
val expectedBackstack = listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment)
|
||||||
|
Assert.assertEquals(expectedBackstack, testNavController.backStack.map { it.destination.id })
|
||||||
val actualArgs = DetailFragmentArgs.fromBundle(testNavController.backStack.last().arguments ?: Bundle())
|
val actualArgs = DetailFragmentArgs.fromBundle(testNavController.backStack.last().arguments ?: Bundle())
|
||||||
Assert.assertEquals(position1, actualArgs.position)
|
Assert.assertEquals(position1, actualArgs.position)
|
||||||
}
|
}
|
||||||
|
|
@ -28,17 +28,6 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
sourceSets {
|
|
||||||
androidTest {
|
|
||||||
java.srcDirs += "src/sharedTest/java"
|
|
||||||
assets.srcDirs += files("$projectDir/schemas".toString())
|
|
||||||
}
|
|
||||||
test {
|
|
||||||
java.srcDirs += "src/sharedTest/java"
|
|
||||||
java.srcDirs += "src/robolectricTest/java"
|
|
||||||
resources.srcDirs += files("$projectDir/schemas".toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// needed for androidTest
|
// needed for androidTest
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
|
|
@ -56,12 +45,8 @@ dependencies {
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||||
|
|
||||||
debugImplementation "androidx.fragment:fragment-testing:1.5.3"
|
|
||||||
|
|
||||||
applyAppTestDependenciesTo(this)
|
applyAppTestDependenciesTo(this)
|
||||||
|
debugImplementation "androidx.fragment:fragment-testing:1.5.3"
|
||||||
testImplementation "androidx.navigation:navigation-testing:$navigation_version"
|
testImplementation project(":examplecase:example-navcontroller-shared-test")
|
||||||
testImplementation project(':test-util-android')
|
androidTestImplementation project(":examplecase:example-navcontroller-shared-test")
|
||||||
androidTestImplementation project(':test-util-android')
|
|
||||||
androidTestImplementation "androidx.navigation:navigation-testing:$navigation_version"
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.examplecase.navcontroller
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class HomeNavigationTest : HomeNavigationSharedTest()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.fnives.test.showcase.examplecase.navcontroller
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class HomeNavigationTest : HomeNavigationSharedTest()
|
||||||
10
gradlescripts/disable.test.task.gradle
Normal file
10
gradlescripts/disable.test.task.gradle
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
project.ext {
|
||||||
|
// helper function to disable Test Tasks in modules where there are no tests and so the Tests are not required.
|
||||||
|
disableTestTasks = { module ->
|
||||||
|
module.tasks.whenTaskAdded { task ->
|
||||||
|
if(task.name.contains("Test")) {
|
||||||
|
task.enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,10 @@ project.ext {
|
||||||
applyAppTestDependenciesTo(this)
|
applyAppTestDependenciesTo(this)
|
||||||
applyComposeTestDependenciesTo(this) // if you are using compose
|
applyComposeTestDependenciesTo(this) // if you are using compose
|
||||||
}
|
}
|
||||||
|
-------------APP-SHARED-TEST(Android Module-------------
|
||||||
|
dependencies {
|
||||||
|
applyAppSharedTestDependenciesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
------------------VERSIONS------------------
|
------------------VERSIONS------------------
|
||||||
versions try to get the global value, if not found they fall back to some defaults.
|
versions try to get the global value, if not found they fall back to some defaults.
|
||||||
|
|
@ -77,18 +81,24 @@ project.ext {
|
||||||
]
|
]
|
||||||
|
|
||||||
// ------------------PRIVATE------------------
|
// ------------------PRIVATE------------------
|
||||||
def applyStandardTestDependenciesTo = { module ->
|
def standardTestDependencies = [
|
||||||
module.dependencies {
|
|
||||||
// coroutine testing
|
// coroutine testing
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$test_coroutines_version"
|
"org.jetbrains.kotlinx:kotlinx-coroutines-test:$test_coroutines_version",
|
||||||
|
|
||||||
// mockito, mocking library
|
// mockito, mocking library
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version"
|
"org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version",
|
||||||
|
|
||||||
testImplementation "io.insert-koin:koin-test-junit5:$testing_koin_version"
|
"io.insert-koin:koin-test-junit5:$testing_koin_version",
|
||||||
// junit5
|
// junit5
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
"org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version",
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$testing_junit5_version"
|
"org.junit.jupiter:junit-jupiter-params:$testing_junit5_version",
|
||||||
|
]
|
||||||
|
|
||||||
|
def applyStandardTestDependenciesTo = { module ->
|
||||||
|
module.dependencies {
|
||||||
|
standardTestDependencies.forEach { dependency ->
|
||||||
|
testImplementation dependency
|
||||||
|
}
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +147,21 @@ project.ext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------APP-SHARED-TEST------------
|
||||||
|
applyAppSharedTestDependenciesTo = { module ->
|
||||||
|
|
||||||
|
module.dependencies {
|
||||||
|
standardTestDependencies.forEach { dependency ->
|
||||||
|
implementation dependency
|
||||||
|
}
|
||||||
|
androidSpecificTestDependencies.forEach { dependency ->
|
||||||
|
implementation dependency
|
||||||
|
}
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$test_coroutines_version"
|
||||||
|
implementation "io.insert-koin:koin-test-junit5:$testing_koin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------COMPOSE------------------
|
// ------------------COMPOSE------------------
|
||||||
applyComposeTestDependenciesTo = { module ->
|
applyComposeTestDependenciesTo = { module ->
|
||||||
module.dependencies {
|
module.dependencies {
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,6 @@ include ':test-util-shared-android'
|
||||||
include ':test-util-shared-robolectric'
|
include ':test-util-shared-robolectric'
|
||||||
include ':test-util-android'
|
include ':test-util-android'
|
||||||
include ':test-util-junit5-android'
|
include ':test-util-junit5-android'
|
||||||
|
include ':app-shared-test'
|
||||||
include ':examplecase:example-navcontroller'
|
include ':examplecase:example-navcontroller'
|
||||||
|
include ':examplecase:example-navcontroller-shared-test'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue