Skip to content

Commit

Permalink
Introduce a new instrumentation library (#155)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Marcel Schnelle authored Mar 18, 2019
1 parent 1d17eb7 commit 3226b13
Show file tree
Hide file tree
Showing 17 changed files with 441 additions and 30 deletions.
45 changes: 24 additions & 21 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"'

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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-*
12 changes: 11 additions & 1 deletion buildSrc/src/main/kotlin/Artifacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 6 additions & 1 deletion buildSrc/src/main/kotlin/Libs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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<out T : Activity> {

/**
Expand Down
108 changes: 108 additions & 0 deletions instrumentation/core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}

tasks.withType<Test> {
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<Deployed> { Artifacts.Instrumentation.Core }
apply(from = "$rootDir/gradle/deployment.gradle")
5 changes: 5 additions & 0 deletions instrumentation/core/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.mannodermaus.junit5">
<application>
<activity android:name=".TestActivity"/>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -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<TestActivity> scenarioExtension = ActivityScenarioExtension.launch(TestActivity.class);

@Test
void testUsingGetScenario() {
ActivityScenario<TestActivity> 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<TestActivity> scenario) {
onView(withText("TestActivity")).check(matches(isDisplayed()));
scenario.onActivity(it -> it.changeText("New Text"));
onView(withText("New Text")).check(matches(isDisplayed()));
}
}
Original file line number Diff line number Diff line change
@@ -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<TestActivity>()

@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<TestActivity>) {
onView(withText("TestActivity")).check(matches(isDisplayed()))
scenario.onActivity { it.changeText("New Text") }
onView(withText("New Text")).check(matches(isDisplayed()))
}
}
Original file line number Diff line number Diff line change
@@ -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<TextView>(R.id.textView) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}

fun changeText(label: String) {
textView.text = label
}
}
13 changes: 13 additions & 0 deletions instrumentation/core/src/androidTest/res/layout/activity_test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#28c">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestActivity"
android:layout_gravity="center"/>
</FrameLayout>
1 change: 1 addition & 0 deletions instrumentation/core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="de.mannodermaus.junit5"/>
Loading

0 comments on commit 3226b13

Please sign in to comment.