From 3226b13108dd4d38fe5d32ad85de4da78674a239 Mon Sep 17 00:00:00 2001 From: Marcel Schnelle Date: Mon, 18 Mar 2019 20:51:41 +0100 Subject: [PATCH] Introduce a new instrumentation library (#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .circleci/config.yml | 45 ++--- buildSrc/src/main/kotlin/Artifacts.kt | 12 +- buildSrc/src/main/kotlin/Environment.kt | 3 +- buildSrc/src/main/kotlin/Libs.kt | 7 +- buildSrc/src/main/kotlin/Versions.kt | 2 + .../de/mannodermaus/junit5/ActivityTest.kt | 9 + instrumentation/core/build.gradle.kts | 108 ++++++++++++ .../core/src/androidTest/AndroidManifest.xml | 5 + .../junit5/JavaInstrumentationTests.java | 31 ++++ .../junit5/KotlinInstrumentationTests.kt | 31 ++++ .../de/mannodermaus/junit5/TestActivities.kt | 20 +++ .../androidTest/res/layout/activity_test.xml | 13 ++ .../core/src/main/AndroidManifest.xml | 1 + .../junit5/ActivityScenarioExtension.kt | 165 ++++++++++++++++++ instrumentation/runner/build.gradle.kts | 4 +- .../de/mannodermaus/junit5/RunnerBuilder.kt | 14 +- instrumentation/settings.gradle.kts | 1 + 17 files changed, 441 insertions(+), 30 deletions(-) create mode 100644 instrumentation/core/build.gradle.kts create mode 100644 instrumentation/core/src/androidTest/AndroidManifest.xml create mode 100644 instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/JavaInstrumentationTests.java create mode 100644 instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/KotlinInstrumentationTests.kt create mode 100644 instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/TestActivities.kt create mode 100644 instrumentation/core/src/androidTest/res/layout/activity_test.xml create mode 100644 instrumentation/core/src/main/AndroidManifest.xml create mode 100644 instrumentation/core/src/main/java/de/mannodermaus/junit5/ActivityScenarioExtension.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ee6820c..bee1a6c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ defaults: &defaults working_directory: ~/root/project docker: - - image: circleci/android:api-28-alpha + - image: circleci/android:api-28 environment: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError"' @@ -30,10 +30,10 @@ jobs: command: cd instrumentation && ./gradlew androidDependencies --no-daemon - run: name: (Instrumentation) Build - command: cd instrumentation && ./gradlew :api:assemble :runner:assemble --stacktrace --no-daemon + command: cd instrumentation && ./gradlew :api:assemble :core:assemble :runner:assemble --stacktrace --no-daemon - run: name: (Instrumentation) Test - command: cd instrumentation && ./gradlew :api:check :runner:check --stacktrace --no-daemon + command: cd instrumentation && ./gradlew :api:check :core:check :runner:check --stacktrace --no-daemon - save_cache: <<: *cache_key @@ -50,6 +50,9 @@ jobs: - store_artifacts: path: instrumentation/api/build/reports destination: instrumentation-api + - store_artifacts: + path: instrumentation/core/build/reports + destination: instrumentation-core - store_artifacts: path: instrumentation/runner/build/reports destination: instrumentation-runner @@ -79,16 +82,16 @@ jobs: name: (Plugin) Deploy command: cd plugin && ./gradlew generatePomFileForLibraryPublication publish :android-junit5:bintrayUpload --stacktrace --no-daemon -# deploy_instrumentation_release: -# <<: *defaults -# steps: -# - attach_workspace: -# at: ~/root -# - restore_cache: -# <<: *cache_key -# - run: -# name: (Instrumentation) Deploy -# command: cd instrumentation && ./gradlew generatePomFileForLibraryPublication publish :api:bintrayUpload :runner:bintrayUpload --stacktrace --no-daemon + deploy_instrumentation_release: + <<: *defaults + steps: + - attach_workspace: + at: ~/root + - restore_cache: + <<: *cache_key + - run: + name: (Instrumentation) Deploy + command: cd instrumentation && ./gradlew generatePomFileForLibraryPublication publish :api:bintrayUpload :core:bintrayUpload :runner:bintrayUpload --stacktrace --no-daemon workflows: version: 2 @@ -110,11 +113,11 @@ workflows: only: master tags: only: plugin-* -# - deploy_instrumentation_release: -# requires: -# - build -# filters: -# branches: -# only: master -# tags: -# only: instrumentation-* + - deploy_instrumentation_release: + requires: + - build + filters: + branches: + only: master + tags: + only: instrumentation-* diff --git a/buildSrc/src/main/kotlin/Artifacts.kt b/buildSrc/src/main/kotlin/Artifacts.kt index afaadce3..3a3ca133 100644 --- a/buildSrc/src/main/kotlin/Artifacts.kt +++ b/buildSrc/src/main/kotlin/Artifacts.kt @@ -46,13 +46,23 @@ object Artifacts { */ object Instrumentation { private val groupId = "de.mannodermaus.junit5" - private val currentVersion = "0.3.0-SNAPSHOT" + private val currentVersion = "1.0.0-SNAPSHOT" val latestStableVersion = "0.2.2" val Library = Deployed( platform = Android(minSdk = 26), groupId = groupId, artifactId = "android-instrumentation-test", + currentVersion = "0.3.0-SNAPSHOT", + latestStableVersion = "0.2.2", + license = license, + description = "(DEPRECATED) Extensions for instrumented Android tests with JUnit 5." + ) + + val Core = Deployed( + platform = Android(minSdk = 14), + groupId = groupId, + artifactId = "android-test-core", currentVersion = currentVersion, latestStableVersion = latestStableVersion, license = license, diff --git a/buildSrc/src/main/kotlin/Environment.kt b/buildSrc/src/main/kotlin/Environment.kt index 06a1a82f..fa18abf5 100644 --- a/buildSrc/src/main/kotlin/Environment.kt +++ b/buildSrc/src/main/kotlin/Environment.kt @@ -5,6 +5,7 @@ object Android { const val targetSdkVersion = 28 const val sampleMinSdkVersion = 14 - val runnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk + val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk + val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Platform.Android).minSdk val instrumentationMinSdkVersion = (Artifacts.Instrumentation.Library.platform as Platform.Android).minSdk } diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index b8c95e21..debfbeff 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -11,10 +11,15 @@ object Libs { const val espresso_core: String = "androidx.test.espresso:espresso-core:" + Versions.espresso_core + /** + * https://developer.android.com/testing */ + const val androidx_test_core: String = "androidx.test:core:" + + Versions.androidx_test_core + /** * https://developer.android.com/testing */ const val androidx_test_runner: String = "androidx.test:runner:" + - Versions.androidx_test_runner + Versions.androidx_test_runner /** * https://developer.android.com/studio */ diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index f83e13a9..cc543375 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,6 +9,8 @@ import kotlin.String object Versions { const val espresso_core: String = "3.1.1" + const val androidx_test_core: String = "1.1.0" + const val androidx_test_runner: String = "1.1.1" const val aapt2: String = "3.2.1-4818971" diff --git a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt b/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt index 89df5b2a..f8f1e4e8 100644 --- a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt +++ b/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt @@ -21,6 +21,13 @@ import kotlin.reflect.KClass /* Constants */ +private const val DEPRECATION_MESSAGE = """ + Please consider moving to "de.mannodermaus.junit5:test-core" instead, + which provides a new API to connect JUnit 5 tests to Android, + based on AndroidX's ActivityScenario. + More info can be found on the android-junit5 repository at https://github.com/mannodermaus/android-junit5. +""" + private const val ABSENT_TARGET_PACKAGE = "-" private const val NO_FLAGS_SET = 0 private const val DEFAULT_LAUNCH_ACTIVITY = true @@ -51,6 +58,7 @@ private const val LOG_TAG = "ActivityTest" * @param launchFlags [Intent] flags to start the Activity under test with * @param launchActivity Whether or not to automatically launch the Activity before the test execution */ +@Deprecated(message = DEPRECATION_MESSAGE) @Retention(RUNTIME) @Target(CLASS, FUNCTION) @ExtendWith(ActivityTestExtension::class) @@ -69,6 +77,7 @@ annotation class ActivityTest( * To obtain an instance, add a parameter of type [Tested] to your test method * and assign it the generic type of the Activity described in the scope's [ActivityTest]. */ +@Deprecated(message = DEPRECATION_MESSAGE) interface Tested { /** diff --git a/instrumentation/core/build.gradle.kts b/instrumentation/core/build.gradle.kts new file mode 100644 index 00000000..8d22bf87 --- /dev/null +++ b/instrumentation/core/build.gradle.kts @@ -0,0 +1,108 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +buildscript { + repositories { + google() + jcenter() + maven("https://oss.sonatype.org/content/repositories/snapshots") + } + + dependencies { + val latest = Artifacts.Plugin.latestStableVersion + classpath("de.mannodermaus.gradle.plugins:android-junit5:$latest") + } +} + +plugins { + id("com.android.library") + kotlin("android") +} + +apply { + plugin("de.mannodermaus.android-junit5") +} + +android { + compileSdkVersion(Android.compileSdkVersion) + + dexOptions { + javaMaxHeapSize = Android.javaMaxHeapSize + } + + defaultConfig { + minSdkVersion(Android.testCoreMinSdkVersion) + targetSdkVersion(Android.targetSdkVersion) + multiDexEnabled = true + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArgument("runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder") + } + + sourceSets { + getByName("main").java.srcDir("src/main/kotlin") + getByName("test").java.srcDir("src/test/kotlin") + getByName("androidTest").java.srcDir("src/androidTest/kotlin") + } + + compileOptions { + setSourceCompatibility(JavaVersion.VERSION_1_8) + setTargetCompatibility(JavaVersion.VERSION_1_8) + } + + lintOptions { + // JUnit 4 refers to java.lang.management APIs, which are absent on Android. + warning("InvalidPackage") + } + + packagingOptions { + exclude("META-INF/LICENSE.md") + exclude("META-INF/LICENSE-notice.md") + } + + testOptions { + unitTests.apply { + isReturnDefaultValues = true + } + } +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} + +tasks.withType { + failFast = true + testLogging { + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + exceptionFormat = TestExceptionFormat.FULL + } +} + +dependencies { + implementation(Libs.junit_jupiter_api) + implementation(Libs.kotlin_stdlib) + implementation(Libs.androidx_test_core) + + // This is required by the "instrumentation-runner" companion library, + // since it can't provide any JUnit 5 runtime libraries itself + // due to fear of prematurely incrementing the minSdkVersion requirement. + runtimeOnly(Libs.junit_platform_runner) + + androidTestImplementation(Libs.junit_jupiter_api) + androidTestImplementation(Libs.espresso_core) + + androidTestRuntimeOnly(project(":runner")) + androidTestRuntimeOnly(Libs.junit_jupiter_engine) +} + +// ------------------------------------------------------------------------------------------------ +// Deployment Setup +// +// Releases are pushed to jcenter via Bintray, while snapshots are pushed to Sonatype OSS. +// This section defines the necessary tasks to push new releases and snapshots using Gradle tasks. +// ------------------------------------------------------------------------------------------------ + +val deployConfig by extra { Artifacts.Instrumentation.Core } +apply(from = "$rootDir/gradle/deployment.gradle") diff --git a/instrumentation/core/src/androidTest/AndroidManifest.xml b/instrumentation/core/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..a010fbd9 --- /dev/null +++ b/instrumentation/core/src/androidTest/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/JavaInstrumentationTests.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/JavaInstrumentationTests.java new file mode 100644 index 00000000..cced974d --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/JavaInstrumentationTests.java @@ -0,0 +1,31 @@ +package de.mannodermaus.junit5; + +import androidx.test.core.app.ActivityScenario; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +class JavaInstrumentationTests { + + @RegisterExtension + final ActivityScenarioExtension scenarioExtension = ActivityScenarioExtension.launch(TestActivity.class); + + @Test + void testUsingGetScenario() { + ActivityScenario scenario = scenarioExtension.getScenario(); + onView(withText("TestActivity")).check(matches(isDisplayed())); + scenario.onActivity(it -> it.changeText("New Text")); + onView(withText("New Text")).check(matches(isDisplayed())); + } + + @Test + void testUsingMethodParameter(ActivityScenario scenario) { + onView(withText("TestActivity")).check(matches(isDisplayed())); + scenario.onActivity(it -> it.changeText("New Text")); + onView(withText("New Text")).check(matches(isDisplayed())); + } +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/KotlinInstrumentationTests.kt b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/KotlinInstrumentationTests.kt new file mode 100644 index 00000000..3dbd56f3 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/KotlinInstrumentationTests.kt @@ -0,0 +1,31 @@ +package de.mannodermaus.junit5 + +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class KotlinInstrumentationTests { + + @JvmField + @RegisterExtension + val scenarioExtension = ActivityScenarioExtension.launch() + + @Test + fun testUsingGetScenario() { + val scenario = scenarioExtension.scenario + onView(withText("TestActivity")).check(matches(isDisplayed())) + scenario.onActivity { it.changeText("New Text") } + onView(withText("New Text")).check(matches(isDisplayed())) + } + + @Test + fun testUsingMethodParameter(scenario: ActivityScenario) { + onView(withText("TestActivity")).check(matches(isDisplayed())) + scenario.onActivity { it.changeText("New Text") } + onView(withText("New Text")).check(matches(isDisplayed())) + } +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/TestActivities.kt b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/TestActivities.kt new file mode 100644 index 00000000..79a6ea66 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/TestActivities.kt @@ -0,0 +1,20 @@ +package de.mannodermaus.junit5 + +import android.app.Activity +import android.os.Bundle +import android.widget.TextView +import de.mannodermaus.junit5.test.R + +class TestActivity : Activity() { + + private val textView by lazy { findViewById(R.id.textView) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test) + } + + fun changeText(label: String) { + textView.text = label + } +} diff --git a/instrumentation/core/src/androidTest/res/layout/activity_test.xml b/instrumentation/core/src/androidTest/res/layout/activity_test.xml new file mode 100644 index 00000000..48ba1f62 --- /dev/null +++ b/instrumentation/core/src/androidTest/res/layout/activity_test.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/instrumentation/core/src/main/AndroidManifest.xml b/instrumentation/core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2b80fac9 --- /dev/null +++ b/instrumentation/core/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/instrumentation/core/src/main/java/de/mannodermaus/junit5/ActivityScenarioExtension.kt b/instrumentation/core/src/main/java/de/mannodermaus/junit5/ActivityScenarioExtension.kt new file mode 100644 index 00000000..7eadf98f --- /dev/null +++ b/instrumentation/core/src/main/java/de/mannodermaus/junit5/ActivityScenarioExtension.kt @@ -0,0 +1,165 @@ +package de.mannodermaus.junit5 + +import android.annotation.TargetApi +import android.app.Activity +import android.content.Intent +import android.os.Build +import androidx.test.core.app.ActivityScenario +import de.mannodermaus.junit5.ActivityScenarioExtension.Companion.launch +import org.junit.jupiter.api.extension.* +import java.lang.reflect.ParameterizedType + +/** + * JUnit 5 Extension for the [ActivityScenario] API, + * provided by the AndroidX test core library. + * + * This extension is used in lieu of a JUnit 4 Rule to automatically + * launch/stop an [ActivityScenario] for each test case. + * + * To use this extension in your test class, add it as a non-private instance field + * to your test class, using one of the factory methods named [launch]. + * Then, annotate this field with JUnit Jupiter's [RegisterExtension] annotation. + * In Kotlin, also add [JvmField] or else the generated property won't be visible to the TestEngine! + * + * ``` + * // Java + * class MyActivityTests { + * + * @RegisterExtension + * final ActivityScenarioExtension scenarioExtension = ActivityScenarioExtension.launch(MyActivity.class); + * } + * + * // Kotlin + * class MyActivityTests { + * + * @JvmField + * @RegisterExtension + * val scenarioExtension = ActivityScenarioExtension.launch() + * } + * ``` + * + * In your test method, you can obtain a reference to the scenario in two ways: + * + * A) You obtain it from the extension directly through its accessor method: + * + * ``` + * // Java + * class MyActivityTests { + * + * @RegisterExtension + * final ActivityScenarioExtension scenarioExtension = ActivityScenarioExtension.launch(MyActivity.class); + * + * @Test + * void myTest() { + * ActivityScenario scenario = scenarioExtension.getScenario(); + * // Use the scenario... + * } + * } + * + * // Kotlin + * class MyActivityTests { + * + * @JvmField + * @RegisterExtension + * val scenarioExtension = ActivityScenarioExtension.launch() + * + * @Test + * fun myTest() { + * val scenario = scenarioExtension.scenario + * // Use the scenario... + * } + * } + * ``` + * + * B) You add a parameter of type [ActivityScenario], with the activity class as its generic type: + * + * ``` + * // Java + * class MyActivityTests { + * + * @RegisterExtension + * final ActivityScenarioExtension scenarioExtension = ActivityScenarioExtension.launch(MyActivity.class); + * + * @Test + * void myTest(ActivityScenario scenario) { + * // Use the scenario... + * } + * } + * + * // Kotlin + * class MyActivityTests { + * + * @JvmField + * @RegisterExtension + * val scenarioExtension = ActivityScenarioExtension.launch() + * + * @Test + * fun myTest(scenario: ActivityScenario) { + * // Use the scenario... + * } + * } + * ``` + * + */ +@TargetApi(Build.VERSION_CODES.O) +class ActivityScenarioExtension +private constructor(private val scenarioSupplier: () -> ActivityScenario) + : BeforeEachCallback, AfterEachCallback, ParameterResolver { + + companion object { + + /** + * Launches an activity of a given class and constructs an [ActivityScenario] for it. + * A default launch intent without specific extras is used to launch the activity. + */ + @JvmStatic + fun launch(activityClass: Class) = + ActivityScenarioExtension { ActivityScenario.launch(activityClass) } + + /** + * Launches an activity of a given class and constructs an [ActivityScenario] for it. + * The given intent is used to launch the activity. + */ + @JvmStatic + fun launch(startActivityIntent: Intent) = + ActivityScenarioExtension { ActivityScenario.launch(startActivityIntent) } + + /* Kotlin-specific convenience variations */ + + /** + * Launches an activity of a given class and constructs an [ActivityScenario] for it. + * A default launch intent without specific extras is used to launch the activity. + */ + inline fun launch() = launch(A::class.java) + } + + /* Fields */ + + private var _scenario: ActivityScenario? = null + + /** + * Returns the current [ActivityScenario] of the activity class. + * @throws NullPointerException If this method is called while no test is running + */ + val scenario: ActivityScenario + get() = _scenario!! + + /* Methods */ + + override fun beforeEach(context: ExtensionContext) { + _scenario = scenarioSupplier() + } + + override fun afterEach(context: ExtensionContext) { + scenario.close() + } + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + // The extension can resolve ActivityScenario parameters that use the correct activity type. + val paramType = parameterContext.parameter.parameterizedType + return paramType is ParameterizedType + && paramType.rawType == ActivityScenario::class.java + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any? = scenario +} diff --git a/instrumentation/runner/build.gradle.kts b/instrumentation/runner/build.gradle.kts index 95f7ab9a..a10b7d67 100644 --- a/instrumentation/runner/build.gradle.kts +++ b/instrumentation/runner/build.gradle.kts @@ -32,10 +32,8 @@ android { } defaultConfig { - minSdkVersion(Android.runnerMinSdkVersion) + minSdkVersion(Android.testRunnerMinSdkVersion) targetSdkVersion(Android.targetSdkVersion) - versionCode = 1 - versionName = "1.0" } sourceSets { diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/RunnerBuilder.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/RunnerBuilder.kt index d2d2089b..732c0fb5 100644 --- a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/RunnerBuilder.kt +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/RunnerBuilder.kt @@ -11,9 +11,17 @@ import org.junit.runners.model.RunnerBuilder * the default JUnit 4-based Runner for Android instrumented tests is, * in a way, tricked into detecting JUnit Jupiter tests as well. * - * Applying the android-junit5 Gradle Plugin will hook this RunnerBuilder - * into the instrumentation runner's lifecycle through a custom - * "testInstrumentationRunnerArgument". + * The RunnerBuilder is added to the instrumentation runner + * through a custom "testInstrumentationRunnerArgument" in the build.gradle script: + * + *
+ *   android {
+ *     defaultConfig {
+ *       testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ *       testInstrumentationRunnerArgument "runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder"
+ *     }
+ *   }
+ * 
* * (Suppressing unused, since this is hooked into the * project configuration via a Test Instrumentation Runner Argument.) diff --git a/instrumentation/settings.gradle.kts b/instrumentation/settings.gradle.kts index 6744add3..26f8b74b 100644 --- a/instrumentation/settings.gradle.kts +++ b/instrumentation/settings.gradle.kts @@ -1,5 +1,6 @@ rootProject.name = "android-junit5-instrumentation" include(":api") +include(":core") include(":runner") include(":sample")