Skip to content

Commit 8d8cfeb

Browse files
authored
Some advancements for Compose integration (#299)
* Update to Compose 1.4.x & Kotlin 1.8.x * Revamp inner workings of ComposeExtension to address prior issues - Rename runComposeTest() to use(), deprecate the former signature - use() can be utilized for lifecycle methods and test methods; it will do the correct thing depennding on the call context - Annotate entry methods with @ExperimentalTestApi - Don't rely on JUnit 4 TestRule anymore, instead re-implement the test execution part using AndroidComposeUiTestEnvironment - Flesh out ComposeContext into a proper type * Update API definition
1 parent 94cdd05 commit 8d8cfeb

File tree

11 files changed

+348
-61
lines changed

11 files changed

+348
-61
lines changed

build-logic/src/main/kotlin/Dependencies.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
object libs {
44
object versions {
5-
const val kotlin = "1.7.0"
5+
const val kotlin = "1.8.21"
66
const val junitJupiter = "5.9.3"
77
const val junitVintage = "5.9.3"
88
const val junitPlatform = "1.9.3"
99
const val truth = "1.1.3"
1010
const val androidXTest = "1.4.0"
11-
const val compose = "1.2.0"
11+
const val composeCompiler = "1.4.7"
12+
const val composeBom = "2023.05.01"
1213
}
1314

1415
object plugins {
@@ -38,10 +39,12 @@ object libs {
3839
const val junitPlatformCommons = "org.junit.platform:junit-platform-commons:${versions.junitPlatform}"
3940
const val junitPlatformRunner = "org.junit.platform:junit-platform-runner:${versions.junitPlatform}"
4041

41-
const val composeUi = "androidx.compose.ui:ui:${versions.compose}"
42-
const val composeUiTooling = "androidx.compose.ui:ui-tooling:${versions.compose}"
43-
const val composeFoundation = "androidx.compose.foundation:foundation:${versions.compose}"
44-
const val composeMaterial = "androidx.compose.material:material:${versions.compose}"
42+
const val composeBom = "androidx.compose:compose-bom:${versions.composeBom}"
43+
const val composeUi = "androidx.compose.ui:ui"
44+
const val composeUiTooling = "androidx.compose.ui:ui-tooling"
45+
const val composeFoundation = "androidx.compose.foundation:foundation"
46+
const val composeMaterial = "androidx.compose.material:material"
47+
const val composeActivity = "androidx.activity:activity-compose:1.7.1"
4548

4649
// Testing
4750
const val junit4 = "junit:junit:4.13.2"
@@ -58,7 +61,7 @@ object libs {
5861
const val androidXTestMonitor = "androidx.test:monitor:${versions.androidXTest}"
5962
const val espressoCore = "androidx.test.espresso:espresso-core:3.4.0"
6063

61-
const val composeUiTest = "androidx.compose.ui:ui-test:${versions.compose}"
62-
const val composeUiTestJUnit4 = "androidx.compose.ui:ui-test-junit4:${versions.compose}"
63-
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${versions.compose}"
64+
const val composeUiTest = "androidx.compose.ui:ui-test"
65+
const val composeUiTestJUnit4 = "androidx.compose.ui:ui-test-junit4"
66+
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest"
6467
}
Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,41 @@
1-
public final class de/mannodermaus/junit5/compose/AndroidComposeExtension : de/mannodermaus/junit5/compose/ComposeExtension, org/junit/jupiter/api/extension/BeforeEachCallback, org/junit/jupiter/api/extension/ParameterResolver {
1+
public final class de/mannodermaus/junit5/compose/AndroidComposeExtension : de/mannodermaus/junit5/compose/ComposeExtension, org/junit/jupiter/api/extension/AfterEachCallback, org/junit/jupiter/api/extension/AfterTestExecutionCallback, org/junit/jupiter/api/extension/BeforeEachCallback, org/junit/jupiter/api/extension/BeforeTestExecutionCallback, org/junit/jupiter/api/extension/ParameterResolver {
22
public static final field $stable I
3-
public fun <init> ()V
3+
public fun afterEach (Lorg/junit/jupiter/api/extension/ExtensionContext;)V
4+
public fun afterTestExecution (Lorg/junit/jupiter/api/extension/ExtensionContext;)V
45
public fun beforeEach (Lorg/junit/jupiter/api/extension/ExtensionContext;)V
6+
public fun beforeTestExecution (Lorg/junit/jupiter/api/extension/ExtensionContext;)V
7+
public final fun getActivity ()Landroidx/activity/ComponentActivity;
8+
public final fun getScenario ()Landroidx/test/core/app/ActivityScenario;
59
public fun resolveParameter (Lorg/junit/jupiter/api/extension/ParameterContext;Lorg/junit/jupiter/api/extension/ExtensionContext;)Ljava/lang/Object;
610
public fun runComposeTest (Lkotlin/jvm/functions/Function1;)V
711
public fun supportsParameter (Lorg/junit/jupiter/api/extension/ParameterContext;Lorg/junit/jupiter/api/extension/ExtensionContext;)Z
12+
public fun use (Lkotlin/jvm/functions/Function1;)V
813
}
914

1015
public final class de/mannodermaus/junit5/compose/AndroidComposeExtensionKt {
1116
public static final fun createAndroidComposeExtension (Ljava/lang/Class;)Lde/mannodermaus/junit5/compose/AndroidComposeExtension;
1217
public static final fun createComposeExtension ()Lde/mannodermaus/junit5/compose/ComposeExtension;
1318
}
1419

20+
public abstract interface class de/mannodermaus/junit5/compose/ComposeContext : androidx/compose/ui/test/SemanticsNodeInteractionsProvider {
21+
public abstract fun awaitIdle (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
22+
public abstract fun getDensity ()Landroidx/compose/ui/unit/Density;
23+
public abstract fun getMainClock ()Landroidx/compose/ui/test/MainTestClock;
24+
public abstract fun registerIdlingResource (Landroidx/compose/ui/test/IdlingResource;)V
25+
public abstract fun runOnIdle (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
26+
public abstract fun runOnUiThread (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
27+
public abstract fun setContent (Lkotlin/jvm/functions/Function2;)V
28+
public abstract fun unregisterIdlingResource (Landroidx/compose/ui/test/IdlingResource;)V
29+
public abstract fun waitForIdle ()V
30+
public abstract fun waitUntil (JLkotlin/jvm/functions/Function0;)V
31+
}
32+
1533
public abstract interface class de/mannodermaus/junit5/compose/ComposeExtension {
1634
public abstract fun runComposeTest (Lkotlin/jvm/functions/Function1;)V
35+
public abstract fun use (Lkotlin/jvm/functions/Function1;)V
36+
}
37+
38+
public final class de/mannodermaus/junit5/compose/ComposeExtension$DefaultImpls {
39+
public static fun runComposeTest (Lde/mannodermaus/junit5/compose/ComposeExtension;Lkotlin/jvm/functions/Function1;)V
1740
}
1841

instrumentation/compose/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ android {
3737
}
3838

3939
composeOptions {
40-
kotlinCompilerExtensionVersion = libs.versions.compose
40+
kotlinCompilerExtensionVersion = libs.versions.composeCompiler
4141
}
4242

4343
testOptions {
@@ -64,13 +64,16 @@ tasks.withType<Test> {
6464
}
6565

6666
dependencies {
67+
implementation(project(":core"))
6768
implementation(libs.kotlinStdLib)
6869
implementation(libs.kotlinCoroutinesCore)
6970

7071
implementation(libs.junitJupiterApi)
7172
implementation(libs.junit4)
7273
implementation(libs.espressoCore)
7374

75+
implementation(platform(libs.composeBom))
76+
implementation(libs.composeActivity)
7477
implementation(libs.composeUi)
7578
implementation(libs.composeUiTooling)
7679
implementation(libs.composeFoundation)
@@ -83,7 +86,6 @@ dependencies {
8386
androidTestImplementation(libs.junitJupiterParams)
8487
androidTestImplementation(libs.espressoCore)
8588

86-
androidTestImplementation(project(":core"))
8789
androidTestRuntimeOnly(project(":runner"))
8890
androidTestRuntimeOnly(libs.androidXTestRunner)
8991
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="de.mannodermaus.junit5.compose">
3+
4+
<application>
5+
<activity android:name=".ExistingActivity" />
6+
</application>
7+
</manifest>

instrumentation/compose/src/androidTest/java/de/mannodermaus/junit5/compose/ClassComposeExtensionTests.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.mannodermaus.junit5.compose
22

3+
import androidx.activity.ComponentActivity
34
import androidx.compose.foundation.layout.Column
45
import androidx.compose.material.Button
56
import androidx.compose.material.Text
@@ -10,6 +11,7 @@ import androidx.compose.runtime.setValue
1011
import androidx.compose.ui.test.assertIsDisplayed
1112
import androidx.compose.ui.test.onNodeWithText
1213
import androidx.compose.ui.test.performClick
14+
import org.junit.jupiter.api.Test
1315
import org.junit.jupiter.api.extension.ExtendWith
1416
import org.junit.jupiter.params.ParameterizedTest
1517
import org.junit.jupiter.params.provider.ValueSource
@@ -25,8 +27,8 @@ class ClassComposeExtensionTests {
2527
]
2628
)
2729
@ParameterizedTest
28-
fun test(buttonLabel: String, extension: AndroidComposeExtension) {
29-
extension.runComposeTest {
30+
fun test(buttonLabel: String, extension: AndroidComposeExtension<ComponentActivity>) =
31+
extension.use {
3032
setContent {
3133
Column {
3234
var counter by remember { mutableStateOf(0) }
@@ -42,5 +44,26 @@ class ClassComposeExtensionTests {
4244
onNodeWithText(buttonLabel).performClick()
4345
onNodeWithText("Clicked: 1").assertIsDisplayed()
4446
}
47+
48+
@Test
49+
fun anotherTest(extension: ComposeExtension) = extension.use {
50+
setContent {
51+
Column {
52+
var showDetails by remember { mutableStateOf(false) }
53+
54+
Text("Hello world")
55+
if (showDetails) {
56+
Text("Extra details")
57+
}
58+
59+
Button(onClick = { showDetails = !showDetails }) {
60+
Text("click")
61+
}
62+
}
63+
}
64+
65+
onNodeWithText("Extra details").assertDoesNotExist()
66+
onNodeWithText("click").performClick()
67+
onNodeWithText("Extra details").assertIsDisplayed()
4568
}
4669
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package de.mannodermaus.junit5.compose
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.material.Button
8+
import androidx.compose.material.Text
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.mutableStateOf
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.runtime.setValue
13+
14+
class ExistingActivity : ComponentActivity() {
15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(savedInstanceState)
17+
18+
setContent {
19+
Column {
20+
var counter by remember { mutableStateOf(0) }
21+
22+
Text(text = "Clicked: $counter")
23+
Button(onClick = { counter++ }) {
24+
Text("click")
25+
}
26+
}
27+
}
28+
}
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package de.mannodermaus.junit5.compose
2+
3+
import androidx.compose.ui.test.ExperimentalTestApi
4+
import androidx.compose.ui.test.assertIsDisplayed
5+
import androidx.compose.ui.test.onNodeWithText
6+
import androidx.compose.ui.test.performClick
7+
import org.junit.jupiter.api.BeforeAll
8+
import org.junit.jupiter.api.BeforeEach
9+
import org.junit.jupiter.api.Test
10+
import org.junit.jupiter.api.TestInstance
11+
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
12+
import org.junit.jupiter.api.extension.RegisterExtension
13+
14+
@TestInstance(PER_CLASS)
15+
class ExistingActivityComposeExtensionTests {
16+
17+
@JvmField
18+
@RegisterExtension
19+
@OptIn(ExperimentalTestApi::class)
20+
val extension = createAndroidComposeExtension<ExistingActivity>()
21+
22+
@BeforeAll
23+
fun beforeAll() = extension.use {
24+
onNodeWithText("click").performClick()
25+
}
26+
27+
@BeforeEach
28+
fun beforeEach() = extension.use {
29+
onNodeWithText("click").performClick()
30+
}
31+
32+
@Test
33+
fun test() = extension.use {
34+
onNodeWithText("Clicked: 2").assertIsDisplayed()
35+
}
36+
}

instrumentation/compose/src/androidTest/java/de/mannodermaus/junit5/compose/FieldComposeExtensionTests.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.runtime.getValue
77
import androidx.compose.runtime.mutableStateOf
88
import androidx.compose.runtime.remember
99
import androidx.compose.runtime.setValue
10+
import androidx.compose.ui.test.ExperimentalTestApi
1011
import androidx.compose.ui.test.assertIsDisplayed
1112
import androidx.compose.ui.test.onNodeWithText
1213
import androidx.compose.ui.test.performClick
@@ -18,6 +19,7 @@ class FieldComposeExtensionTests {
1819

1920
@JvmField
2021
@RegisterExtension
22+
@OptIn(ExperimentalTestApi::class)
2123
val extension = createComposeExtension()
2224

2325
@ValueSource(
@@ -28,22 +30,20 @@ class FieldComposeExtensionTests {
2830
]
2931
)
3032
@ParameterizedTest
31-
fun test(buttonLabel: String) {
32-
extension.runComposeTest {
33-
setContent {
34-
Column {
35-
var counter by remember { mutableStateOf(0) }
33+
fun test(buttonLabel: String) = extension.use {
34+
setContent {
35+
Column {
36+
var counter by remember { mutableStateOf(0) }
3637

37-
Text(text = "Clicked: $counter")
38-
Button(onClick = { counter++ }) {
39-
Text(text = buttonLabel)
40-
}
38+
Text(text = "Clicked: $counter")
39+
Button(onClick = { counter++ }) {
40+
Text(text = buttonLabel)
4141
}
4242
}
43-
44-
onNodeWithText("Clicked: 0").assertIsDisplayed()
45-
onNodeWithText(buttonLabel).performClick()
46-
onNodeWithText("Clicked: 1").assertIsDisplayed()
4743
}
44+
45+
onNodeWithText("Clicked: 0").assertIsDisplayed()
46+
onNodeWithText(buttonLabel).performClick()
47+
onNodeWithText("Clicked: 1").assertIsDisplayed()
4848
}
4949
}

0 commit comments

Comments
 (0)