Skip to content

Commit 38ef72c

Browse files
authored
Allow autoconfiguration of Compose integration via the plugin (#327)
It needs to detect an androidx.compose artifact in the androidTestImplementation configuration for this to work, but from there it sets itself up automatically, just like the main libraries do when they see junit-jupiter-api
1 parent d082fce commit 38ef72c

File tree

8 files changed

+220
-32
lines changed

8 files changed

+220
-32
lines changed

README.md.template

+35-9
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ Can you think of more? Let's discuss in the issues section!
149149
<summary>Kotlin</summary>
150150

151151
```kotlin
152-
dependencies {
153-
androidTestImplementation("de.mannodermaus.junit5:android-test-extensions:${instrumentationVersion}")
152+
junitPlatform {
153+
instrumentationTests.includeExtensions.set(true)
154154
}
155155
```
156156
</details>
@@ -159,24 +159,25 @@ Can you think of more? Let's discuss in the issues section!
159159
<summary>Groovy</summary>
160160

161161
```groovy
162-
dependencies {
163-
androidTestImplementation "de.mannodermaus.junit5:android-test-extensions:${instrumentationVersion}"
162+
junitPlatform {
163+
instrumentationTests.includeExtensions.set(true)
164164
}
165165
```
166166
</details>
167167

168168
### Jetpack Compose
169169

170170
To test `@Composable` functions on device with JUnit 5, first enable support for instrumentation tests as described above.
171-
Then, add the integration library for Jetpack Compose to the `androidTestImplementation` configuration:
171+
Then, add the Compose test dependency to your `androidTestImplementation` configuration
172+
and the plugin will autoconfigure JUnit 5 Compose support for you!
172173

173174
<details open>
174175
<summary>Kotlin</summary>
175176

176177
```kotlin
177178
dependencies {
178-
// Test extension & transitive dependencies
179-
androidTestImplementation("de.mannodermaus.junit5:android-test-compose:${instrumentationVersion}")
179+
// Compose test framework
180+
androidTestImplementation("androidx.compose.ui:ui-test-android:$compose_version")
180181

181182
// Needed for createComposeExtension() and createAndroidComposeExtension()
182183
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
@@ -189,8 +190,8 @@ Then, add the integration library for Jetpack Compose to the `androidTestImpleme
189190

190191
```groovy
191192
dependencies {
192-
// Test extension & transitive dependencies
193-
androidTestImplementation "de.mannodermaus.junit5:android-test-compose:${instrumentationVersion}"
193+
// Compose test framework
194+
androidTestImplementation "androidx.compose.ui:ui-test-android:$compose_version"
194195

195196
// Needed for createComposeExtension() and createAndroidComposeExtension()
196197
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
@@ -200,6 +201,31 @@ Then, add the integration library for Jetpack Compose to the `androidTestImpleme
200201

201202
[The wiki][wiki-home] includes a section on how to test your Composables with JUnit 5.
202203

204+
### Override the version of instrumentation test libraries
205+
206+
By default, the plugin will make sure to use a compatible version of the instrumentation test libraries
207+
when it sets up the artifacts automatically. However, it is possible to choose a custom version instead via its DSL:
208+
209+
<details open>
210+
<summary>Kotlin</summary>
211+
212+
```kotlin
213+
junitPlatform {
214+
instrumentationTests.version.set("${instrumentationVersion}")
215+
}
216+
```
217+
</details>
218+
219+
<details>
220+
<summary>Groovy</summary>
221+
222+
```groovy
223+
junitPlatform {
224+
instrumentationTests.version.set("${instrumentationVersion}")
225+
}
226+
```
227+
</details>
228+
203229
## Official Support
204230

205231
At this time, Google hasn't shared any immediate plans to bring first-party support for JUnit 5 to Android. The following list is an aggregation of pending feature requests:

plugin/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Change Log
77
- Allow overriding the version of the instrumentation libraries applied with the plugin
88
- Update Jacoco & instrumentation test DSLs of the plugin to use Gradle Providers for their input parameters (e.g. `instrumentationTests.enabled.set(true)` instead of `instrumentationTests.enabled = true`)
99
- Removed deprecated `integrityCheckEnabled` flag from the plugin DSL's instrumentation test options
10+
- Allow opt-in usage of extension library via the plugin's DSL
11+
- Allow autoconfiguration of compose library if Compose is used in the androidTest dependency list
1012

1113
## 1.10.0.0 (2023-11-05)
1214
- JUnit 5.10.0

plugin/android-junit5/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ val versionClassTask = tasks.register<Copy>("createVersionClass") {
8888
mapOf(
8989
"tokens" to mapOf(
9090
"INSTRUMENTATION_GROUP" to Artifacts.Instrumentation.groupId,
91+
"INSTRUMENTATION_COMPOSE" to Artifacts.Instrumentation.Compose.artifactId,
9192
"INSTRUMENTATION_CORE" to Artifacts.Instrumentation.Core.artifactId,
9293
"INSTRUMENTATION_EXTENSIONS" to Artifacts.Instrumentation.Extensions.artifactId,
9394
"INSTRUMENTATION_RUNNER" to Artifacts.Instrumentation.Runner.artifactId,

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt

+12-5
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ import de.mannodermaus.gradle.plugins.junit5.internal.config.PluginConfig
1414
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
1515
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getAsList
1616
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getTaskName
17+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.hasDependency
1718
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junit5Warn
1819
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.namedOrNull
20+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.usesComposeIn
21+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.usesJUnitJupiterIn
1922
import de.mannodermaus.gradle.plugins.junit5.internal.utils.excludedPackagingOptions
2023
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5JacocoReport
2124
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5WriteFilters
2225
import org.gradle.api.Project
26+
import org.gradle.api.artifacts.Dependency
2327
import org.gradle.api.tasks.testing.Test
2428

2529
internal fun configureJUnit5(
@@ -109,11 +113,7 @@ private fun AndroidJUnitPlatformExtension.prepareInstrumentationTests(project: P
109113
if (!instrumentationTests.enabled.get()) return
110114

111115
// Automatically configure instrumentation tests when JUnit 5 is detected in that configuration
112-
val hasJupiterApi = project.configurations
113-
.getByName("androidTestImplementation")
114-
.dependencies
115-
.any { it.group == "org.junit.jupiter" && it.name == "junit-jupiter-api" }
116-
if (!hasJupiterApi) return
116+
if (!project.usesJUnitJupiterIn("androidTestImplementation")) return
117117

118118
// Attach the JUnit 5 RunnerBuilder to the list, unless it's already added
119119
val runnerBuilders = android.defaultConfig.testInstrumentationRunnerArguments.getAsList("runnerBuilder")
@@ -141,6 +141,13 @@ private fun AndroidJUnitPlatformExtension.prepareInstrumentationTests(project: P
141141
"${Libraries.instrumentationExtensions}:$version"
142142
)
143143
}
144+
145+
if (project.usesComposeIn("androidTestImplementation")) {
146+
project.dependencies.add(
147+
"androidTestImplementation",
148+
"${Libraries.instrumentationCompose}:$version"
149+
)
150+
}
144151
}
145152

146153
private fun AndroidJUnitPlatformExtension.configureUnitTests(project: Project, variant: Variant) {

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/extensions/ProjectExt.kt

+19
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.android.build.gradle.BasePlugin
55
import de.mannodermaus.gradle.plugins.junit5.dsl.AndroidJUnitPlatformExtension
66
import de.mannodermaus.gradle.plugins.junit5.internal.config.EXTENSION_NAME
77
import org.gradle.api.Project
8+
import org.gradle.api.artifacts.Dependency
89
import java.util.concurrent.atomic.AtomicBoolean
910
import kotlin.contracts.ExperimentalContracts
1011
import kotlin.contracts.InvocationKind
@@ -35,3 +36,21 @@ internal fun Project.whenAndroidPluginAdded(block: (BasePlugin) -> Unit) {
3536
}
3637
}
3738
}
39+
40+
internal fun Project.hasDependency(configurationName: String, matching: (Dependency) -> Boolean): Boolean {
41+
val configuration = project.configurations.getByName(configurationName)
42+
43+
return configuration.dependencies.any(matching)
44+
}
45+
46+
internal fun Project.usesJUnitJupiterIn(configurationName: String): Boolean {
47+
return project.hasDependency(configurationName) {
48+
it.group == "org.junit.jupiter" && it.name == "junit-jupiter-api"
49+
}
50+
}
51+
52+
internal fun Project.usesComposeIn(configurationName: String): Boolean {
53+
return project.hasDependency(configurationName) {
54+
it.group?.startsWith("androidx.compose") ?: false
55+
}
56+
}

plugin/android-junit5/src/main/templates/Libraries.kt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.mannodermaus
22

33
internal object Libraries {
44
const val instrumentationVersion = "@INSTRUMENTATION_VERSION@"
5+
const val instrumentationCompose = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_COMPOSE@"
56
const val instrumentationCore = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_CORE@"
67
const val instrumentationExtensions = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_EXTENSIONS@"
78
const val instrumentationRunner = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_RUNNER@"

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/plugin/InstrumentationSupportTests.kt

+82-18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import de.mannodermaus.Libraries
55
import de.mannodermaus.gradle.plugins.junit5.internal.config.ANDROID_JUNIT5_RUNNER_BUILDER_CLASS
66
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
77
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junitPlatform
8+
import de.mannodermaus.gradle.plugins.junit5.util.assertThat
89
import de.mannodermaus.gradle.plugins.junit5.util.evaluate
910
import org.gradle.api.Project
1011
import org.junit.jupiter.api.BeforeEach
@@ -67,14 +68,15 @@ class InstrumentationSupportTests {
6768
/* Dependencies */
6869

6970
@Test
70-
fun `add the dependencies`() {
71+
fun `add only the main dependencies`() {
7172
project.addJUnitJupiterApi()
7273
project.evaluate()
7374

74-
assertThat(project.dependencyNamed("androidTestImplementation", "android-test-core"))
75-
.isEqualTo("${Libraries.instrumentationCore}:${Libraries.instrumentationVersion}")
76-
assertThat(project.dependencyNamed("androidTestRuntimeOnly", "android-test-runner"))
77-
.isEqualTo("${Libraries.instrumentationRunner}:${Libraries.instrumentationVersion}")
75+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary())
76+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary())
77+
78+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(extensionsLibrary())
79+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(composeLibrary())
7880
}
7981

8082
@Test
@@ -83,8 +85,8 @@ class InstrumentationSupportTests {
8385
project.junitPlatform.instrumentationTests.version.set("1.3.3.7")
8486
project.evaluate()
8587

86-
assertThat(project.dependencyNamed("androidTestImplementation", "android-test-core")).endsWith("1.3.3.7")
87-
assertThat(project.dependencyNamed("androidTestRuntimeOnly", "android-test-runner")).endsWith("1.3.3.7")
88+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary("1.3.3.7"))
89+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary("1.3.3.7"))
8890
}
8991

9092
@Test
@@ -96,8 +98,8 @@ class InstrumentationSupportTests {
9698
project.dependencies.add("androidTestRuntimeOnly", addedRunner)
9799
project.evaluate()
98100

99-
assertThat(project.dependencyNamed("androidTestImplementation", "android-test-core")).isEqualTo(addedCore)
100-
assertThat(project.dependencyNamed("androidTestRuntimeOnly", "android-test-runner")).isEqualTo(addedRunner)
101+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary("0.1.3.3.7"))
102+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary("0.1.3.3.7"))
101103
}
102104

103105
@Test
@@ -106,16 +108,61 @@ class InstrumentationSupportTests {
106108
project.junitPlatform.instrumentationTests.enabled.set(false)
107109
project.evaluate()
108110

109-
assertThat(project.dependencyNamed("androidTestImplementation", "android-test-core")).isNull()
110-
assertThat(project.dependencyNamed("androidTestRuntimeOnly", "android-test-runner")).isNull()
111+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(coreLibrary(null))
112+
assertThat(project).configuration("androidTestRuntimeOnly").doesNotHaveDependency(runnerLibrary(null))
111113
}
112114

113115
@Test
114116
fun `do not add the dependencies when Jupiter is not added`() {
115117
project.evaluate()
116118

117-
assertThat(project.dependencyNamed("androidTestImplementation", "android-test-core")).isNull()
118-
assertThat(project.dependencyNamed("androidTestRuntimeOnly", "android-test-runner")).isNull()
119+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(coreLibrary(null))
120+
assertThat(project).configuration("androidTestRuntimeOnly").doesNotHaveDependency(runnerLibrary(null))
121+
}
122+
123+
@Test
124+
fun `do not add the dependencies when Jupiter is not added, even if extension is configured to be added`() {
125+
project.junitPlatform.instrumentationTests.includeExtensions.set(true)
126+
project.evaluate()
127+
128+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(coreLibrary(null))
129+
assertThat(project).configuration("androidTestImplementation").doesNotHaveDependency(extensionsLibrary(null))
130+
assertThat(project).configuration("androidTestRuntimeOnly").doesNotHaveDependency(runnerLibrary(null))
131+
}
132+
133+
@Test
134+
fun `add the extension library if configured`() {
135+
project.addJUnitJupiterApi()
136+
project.junitPlatform.instrumentationTests.includeExtensions.set(true)
137+
project.evaluate()
138+
139+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary())
140+
assertThat(project).configuration("androidTestImplementation").hasDependency(extensionsLibrary())
141+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary())
142+
}
143+
144+
@Test
145+
fun `add the compose library if configured`() {
146+
project.addJUnitJupiterApi()
147+
project.addCompose()
148+
project.evaluate()
149+
150+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary())
151+
assertThat(project).configuration("androidTestImplementation").hasDependency(composeLibrary())
152+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary())
153+
}
154+
155+
@Test
156+
fun `add the extensions and compose libraries if configured`() {
157+
project.addJUnitJupiterApi()
158+
project.addCompose()
159+
project.junitPlatform.instrumentationTests.includeExtensions.set(true)
160+
project.evaluate()
161+
162+
assertThat(project).configuration("androidTestImplementation").hasDependency(coreLibrary())
163+
assertThat(project).configuration("androidTestImplementation").hasDependency(composeLibrary())
164+
assertThat(project).configuration("androidTestImplementation").hasDependency(extensionsLibrary())
165+
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary())
119166
}
120167

121168
/* Private */
@@ -124,10 +171,27 @@ class InstrumentationSupportTests {
124171
dependencies.add("androidTestImplementation", "org.junit.jupiter:junit-jupiter-api:+")
125172
}
126173

127-
private fun Project.dependencyNamed(configurationName: String, name: String): String? {
128-
return configurations.getByName(configurationName)
129-
.dependencies
130-
.firstOrNull { it.name == name }
131-
?.run { "$group:$name:$version" }
174+
private fun Project.addCompose() {
175+
dependencies.add("androidTestImplementation", "androidx.compose.ui:ui-test-android:+")
176+
}
177+
178+
private fun composeLibrary(withVersion: String? = Libraries.instrumentationVersion) =
179+
library(Libraries.instrumentationCompose, withVersion)
180+
181+
private fun coreLibrary(withVersion: String? = Libraries.instrumentationVersion) =
182+
library(Libraries.instrumentationCore, withVersion)
183+
184+
private fun extensionsLibrary(withVersion: String? = Libraries.instrumentationVersion) =
185+
library(Libraries.instrumentationExtensions, withVersion)
186+
187+
private fun runnerLibrary(withVersion: String? = Libraries.instrumentationVersion) =
188+
library(Libraries.instrumentationRunner, withVersion)
189+
190+
private fun library(artifactId: String, version: String?) = buildString {
191+
append(artifactId)
192+
if (version != null) {
193+
append(':')
194+
append(version)
195+
}
132196
}
133197
}

0 commit comments

Comments
 (0)