Skip to content

Commit 3226b13

Browse files
author
Marcel Schnelle
authored
Introduce a new instrumentation library (#155)
* Deprecate public API in “api” module No further development will take place here, as the testing model for Android moves to a unified approach through ActivityScenario. There’s going to be a new artifact addressing this new API. * Introduce new “core” library, alongside a JUnit 5 implementation of ActivityScenario This is a JUnit 5 Extension which delegates to the ActivityScenario API - pretty straight-forward. Furthermore, add some (Espresso) tests to verify the behavior * Update CircleCI config to include the new module
1 parent 1d17eb7 commit 3226b13

File tree

17 files changed

+441
-30
lines changed

17 files changed

+441
-30
lines changed

.circleci/config.yml

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
defaults: &defaults
33
working_directory: ~/root/project
44
docker:
5-
- image: circleci/android:api-28-alpha
5+
- image: circleci/android:api-28
66
environment:
77
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError"'
88

@@ -30,10 +30,10 @@ jobs:
3030
command: cd instrumentation && ./gradlew androidDependencies --no-daemon
3131
- run:
3232
name: (Instrumentation) Build
33-
command: cd instrumentation && ./gradlew :api:assemble :runner:assemble --stacktrace --no-daemon
33+
command: cd instrumentation && ./gradlew :api:assemble :core:assemble :runner:assemble --stacktrace --no-daemon
3434
- run:
3535
name: (Instrumentation) Test
36-
command: cd instrumentation && ./gradlew :api:check :runner:check --stacktrace --no-daemon
36+
command: cd instrumentation && ./gradlew :api:check :core:check :runner:check --stacktrace --no-daemon
3737

3838
- save_cache:
3939
<<: *cache_key
@@ -50,6 +50,9 @@ jobs:
5050
- store_artifacts:
5151
path: instrumentation/api/build/reports
5252
destination: instrumentation-api
53+
- store_artifacts:
54+
path: instrumentation/core/build/reports
55+
destination: instrumentation-core
5356
- store_artifacts:
5457
path: instrumentation/runner/build/reports
5558
destination: instrumentation-runner
@@ -79,16 +82,16 @@ jobs:
7982
name: (Plugin) Deploy
8083
command: cd plugin && ./gradlew generatePomFileForLibraryPublication publish :android-junit5:bintrayUpload --stacktrace --no-daemon
8184

82-
# deploy_instrumentation_release:
83-
# <<: *defaults
84-
# steps:
85-
# - attach_workspace:
86-
# at: ~/root
87-
# - restore_cache:
88-
# <<: *cache_key
89-
# - run:
90-
# name: (Instrumentation) Deploy
91-
# command: cd instrumentation && ./gradlew generatePomFileForLibraryPublication publish :api:bintrayUpload :runner:bintrayUpload --stacktrace --no-daemon
85+
deploy_instrumentation_release:
86+
<<: *defaults
87+
steps:
88+
- attach_workspace:
89+
at: ~/root
90+
- restore_cache:
91+
<<: *cache_key
92+
- run:
93+
name: (Instrumentation) Deploy
94+
command: cd instrumentation && ./gradlew generatePomFileForLibraryPublication publish :api:bintrayUpload :core:bintrayUpload :runner:bintrayUpload --stacktrace --no-daemon
9295

9396
workflows:
9497
version: 2
@@ -110,11 +113,11 @@ workflows:
110113
only: master
111114
tags:
112115
only: plugin-*
113-
# - deploy_instrumentation_release:
114-
# requires:
115-
# - build
116-
# filters:
117-
# branches:
118-
# only: master
119-
# tags:
120-
# only: instrumentation-*
116+
- deploy_instrumentation_release:
117+
requires:
118+
- build
119+
filters:
120+
branches:
121+
only: master
122+
tags:
123+
only: instrumentation-*

buildSrc/src/main/kotlin/Artifacts.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,23 @@ object Artifacts {
4646
*/
4747
object Instrumentation {
4848
private val groupId = "de.mannodermaus.junit5"
49-
private val currentVersion = "0.3.0-SNAPSHOT"
49+
private val currentVersion = "1.0.0-SNAPSHOT"
5050
val latestStableVersion = "0.2.2"
5151

5252
val Library = Deployed(
5353
platform = Android(minSdk = 26),
5454
groupId = groupId,
5555
artifactId = "android-instrumentation-test",
56+
currentVersion = "0.3.0-SNAPSHOT",
57+
latestStableVersion = "0.2.2",
58+
license = license,
59+
description = "(DEPRECATED) Extensions for instrumented Android tests with JUnit 5."
60+
)
61+
62+
val Core = Deployed(
63+
platform = Android(minSdk = 14),
64+
groupId = groupId,
65+
artifactId = "android-test-core",
5666
currentVersion = currentVersion,
5767
latestStableVersion = latestStableVersion,
5868
license = license,

buildSrc/src/main/kotlin/Environment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ object Android {
55

66
const val targetSdkVersion = 28
77
const val sampleMinSdkVersion = 14
8-
val runnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk
8+
val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk
9+
val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Platform.Android).minSdk
910
val instrumentationMinSdkVersion = (Artifacts.Instrumentation.Library.platform as Platform.Android).minSdk
1011
}

buildSrc/src/main/kotlin/Libs.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ object Libs {
1111
const val espresso_core: String = "androidx.test.espresso:espresso-core:" +
1212
Versions.espresso_core
1313

14+
/**
15+
* https://developer.android.com/testing */
16+
const val androidx_test_core: String = "androidx.test:core:" +
17+
Versions.androidx_test_core
18+
1419
/**
1520
* https://developer.android.com/testing */
1621
const val androidx_test_runner: String = "androidx.test:runner:" +
17-
Versions.androidx_test_runner
22+
Versions.androidx_test_runner
1823

1924
/**
2025
* https://developer.android.com/studio */

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import kotlin.String
99
object Versions {
1010
const val espresso_core: String = "3.1.1"
1111

12+
const val androidx_test_core: String = "1.1.0"
13+
1214
const val androidx_test_runner: String = "1.1.1"
1315

1416
const val aapt2: String = "3.2.1-4818971"

instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ import kotlin.reflect.KClass
2121

2222
/* Constants */
2323

24+
private const val DEPRECATION_MESSAGE = """
25+
Please consider moving to "de.mannodermaus.junit5:test-core" instead,
26+
which provides a new API to connect JUnit 5 tests to Android,
27+
based on AndroidX's ActivityScenario.
28+
More info can be found on the android-junit5 repository at https://github.com/mannodermaus/android-junit5.
29+
"""
30+
2431
private const val ABSENT_TARGET_PACKAGE = "-"
2532
private const val NO_FLAGS_SET = 0
2633
private const val DEFAULT_LAUNCH_ACTIVITY = true
@@ -51,6 +58,7 @@ private const val LOG_TAG = "ActivityTest"
5158
* @param launchFlags [Intent] flags to start the Activity under test with
5259
* @param launchActivity Whether or not to automatically launch the Activity before the test execution
5360
*/
61+
@Deprecated(message = DEPRECATION_MESSAGE)
5462
@Retention(RUNTIME)
5563
@Target(CLASS, FUNCTION)
5664
@ExtendWith(ActivityTestExtension::class)
@@ -69,6 +77,7 @@ annotation class ActivityTest(
6977
* To obtain an instance, add a parameter of type [Tested] to your test method
7078
* and assign it the generic type of the Activity described in the scope's [ActivityTest].
7179
*/
80+
@Deprecated(message = DEPRECATION_MESSAGE)
7281
interface Tested<out T : Activity> {
7382

7483
/**
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
2+
import org.gradle.api.tasks.testing.logging.TestLogEvent
3+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4+
5+
buildscript {
6+
repositories {
7+
google()
8+
jcenter()
9+
maven("https://oss.sonatype.org/content/repositories/snapshots")
10+
}
11+
12+
dependencies {
13+
val latest = Artifacts.Plugin.latestStableVersion
14+
classpath("de.mannodermaus.gradle.plugins:android-junit5:$latest")
15+
}
16+
}
17+
18+
plugins {
19+
id("com.android.library")
20+
kotlin("android")
21+
}
22+
23+
apply {
24+
plugin("de.mannodermaus.android-junit5")
25+
}
26+
27+
android {
28+
compileSdkVersion(Android.compileSdkVersion)
29+
30+
dexOptions {
31+
javaMaxHeapSize = Android.javaMaxHeapSize
32+
}
33+
34+
defaultConfig {
35+
minSdkVersion(Android.testCoreMinSdkVersion)
36+
targetSdkVersion(Android.targetSdkVersion)
37+
multiDexEnabled = true
38+
39+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
40+
testInstrumentationRunnerArgument("runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder")
41+
}
42+
43+
sourceSets {
44+
getByName("main").java.srcDir("src/main/kotlin")
45+
getByName("test").java.srcDir("src/test/kotlin")
46+
getByName("androidTest").java.srcDir("src/androidTest/kotlin")
47+
}
48+
49+
compileOptions {
50+
setSourceCompatibility(JavaVersion.VERSION_1_8)
51+
setTargetCompatibility(JavaVersion.VERSION_1_8)
52+
}
53+
54+
lintOptions {
55+
// JUnit 4 refers to java.lang.management APIs, which are absent on Android.
56+
warning("InvalidPackage")
57+
}
58+
59+
packagingOptions {
60+
exclude("META-INF/LICENSE.md")
61+
exclude("META-INF/LICENSE-notice.md")
62+
}
63+
64+
testOptions {
65+
unitTests.apply {
66+
isReturnDefaultValues = true
67+
}
68+
}
69+
}
70+
71+
tasks.withType<KotlinCompile> {
72+
kotlinOptions.jvmTarget = "1.8"
73+
}
74+
75+
tasks.withType<Test> {
76+
failFast = true
77+
testLogging {
78+
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
79+
exceptionFormat = TestExceptionFormat.FULL
80+
}
81+
}
82+
83+
dependencies {
84+
implementation(Libs.junit_jupiter_api)
85+
implementation(Libs.kotlin_stdlib)
86+
implementation(Libs.androidx_test_core)
87+
88+
// This is required by the "instrumentation-runner" companion library,
89+
// since it can't provide any JUnit 5 runtime libraries itself
90+
// due to fear of prematurely incrementing the minSdkVersion requirement.
91+
runtimeOnly(Libs.junit_platform_runner)
92+
93+
androidTestImplementation(Libs.junit_jupiter_api)
94+
androidTestImplementation(Libs.espresso_core)
95+
96+
androidTestRuntimeOnly(project(":runner"))
97+
androidTestRuntimeOnly(Libs.junit_jupiter_engine)
98+
}
99+
100+
// ------------------------------------------------------------------------------------------------
101+
// Deployment Setup
102+
//
103+
// Releases are pushed to jcenter via Bintray, while snapshots are pushed to Sonatype OSS.
104+
// This section defines the necessary tasks to push new releases and snapshots using Gradle tasks.
105+
// ------------------------------------------------------------------------------------------------
106+
107+
val deployConfig by extra<Deployed> { Artifacts.Instrumentation.Core }
108+
apply(from = "$rootDir/gradle/deployment.gradle")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.mannodermaus.junit5">
2+
<application>
3+
<activity android:name=".TestActivity"/>
4+
</application>
5+
</manifest>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package de.mannodermaus.junit5;
2+
3+
import androidx.test.core.app.ActivityScenario;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.RegisterExtension;
6+
7+
import static androidx.test.espresso.Espresso.onView;
8+
import static androidx.test.espresso.assertion.ViewAssertions.matches;
9+
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
10+
import static androidx.test.espresso.matcher.ViewMatchers.withText;
11+
12+
class JavaInstrumentationTests {
13+
14+
@RegisterExtension
15+
final ActivityScenarioExtension<TestActivity> scenarioExtension = ActivityScenarioExtension.launch(TestActivity.class);
16+
17+
@Test
18+
void testUsingGetScenario() {
19+
ActivityScenario<TestActivity> scenario = scenarioExtension.getScenario();
20+
onView(withText("TestActivity")).check(matches(isDisplayed()));
21+
scenario.onActivity(it -> it.changeText("New Text"));
22+
onView(withText("New Text")).check(matches(isDisplayed()));
23+
}
24+
25+
@Test
26+
void testUsingMethodParameter(ActivityScenario<TestActivity> scenario) {
27+
onView(withText("TestActivity")).check(matches(isDisplayed()));
28+
scenario.onActivity(it -> it.changeText("New Text"));
29+
onView(withText("New Text")).check(matches(isDisplayed()));
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package de.mannodermaus.junit5
2+
3+
import androidx.test.core.app.ActivityScenario
4+
import androidx.test.espresso.Espresso.onView
5+
import androidx.test.espresso.assertion.ViewAssertions.matches
6+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
7+
import androidx.test.espresso.matcher.ViewMatchers.withText
8+
import org.junit.jupiter.api.Test
9+
import org.junit.jupiter.api.extension.RegisterExtension
10+
11+
class KotlinInstrumentationTests {
12+
13+
@JvmField
14+
@RegisterExtension
15+
val scenarioExtension = ActivityScenarioExtension.launch<TestActivity>()
16+
17+
@Test
18+
fun testUsingGetScenario() {
19+
val scenario = scenarioExtension.scenario
20+
onView(withText("TestActivity")).check(matches(isDisplayed()))
21+
scenario.onActivity { it.changeText("New Text") }
22+
onView(withText("New Text")).check(matches(isDisplayed()))
23+
}
24+
25+
@Test
26+
fun testUsingMethodParameter(scenario: ActivityScenario<TestActivity>) {
27+
onView(withText("TestActivity")).check(matches(isDisplayed()))
28+
scenario.onActivity { it.changeText("New Text") }
29+
onView(withText("New Text")).check(matches(isDisplayed()))
30+
}
31+
}

0 commit comments

Comments
 (0)