Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dcfa54a

Browse files
Manuel Vivomanuelvicnt
Manuel Vivo
authored andcommittedDec 16, 2019
Adds UI tests setup
1 parent 85f8d90 commit dcfa54a

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed
 

‎JetNews/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ on and off, light and dark version in the Android Studio Preview.
7575

7676
The data in the sample is static, held in the `com.example.jetnews.data` package.
7777

78+
### UI testing
79+
80+
Run UI tests from Android Studio or with the `./gradlew connectedCheck` command.
81+
7882
## License
7983

8084
```

‎JetNews/app/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ android {
2626
versionCode 1
2727
versionName "1.0"
2828
vectorDrawables.useSupportLibrary = true
29+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2930
}
3031
buildTypes {
3132
release {
@@ -64,6 +65,13 @@ dependencies {
6465
implementation('androidx.appcompat:appcompat:1.1.0')
6566
implementation('androidx.activity:activity-ktx:1.0.0')
6667
implementation "androidx.core:core-ktx:1.1.0"
68+
69+
androidTestImplementation("junit:junit:4.12")
70+
androidTestImplementation("androidx.test:rules:1.2.0")
71+
androidTestImplementation("androidx.test:runner:1.2.0")
72+
androidTestImplementation("androidx.ui:ui-platform:$compose_version")
73+
androidTestImplementation("androidx.ui:ui-test:$compose_version")
74+
6775
ktlint "com.pinterest:ktlint:0.35.0"
6876
}
6977

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2019 Google, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.jetnews
18+
19+
import androidx.test.filters.MediumTest
20+
import androidx.ui.test.assertIsDisplayed
21+
import androidx.ui.test.doClick
22+
import androidx.ui.test.findByText
23+
import com.example.jetnews.util.JetnewsComposeTestRule
24+
import com.example.jetnews.util.findAllByText
25+
import com.example.jetnews.util.goBack
26+
import com.example.jetnews.util.launchJetNewsApp
27+
import com.example.jetnews.util.workForComposeToBeIdle
28+
import org.junit.Before
29+
import org.junit.Rule
30+
import org.junit.Test
31+
import org.junit.runner.RunWith
32+
import org.junit.runners.JUnit4
33+
34+
@MediumTest
35+
@RunWith(JUnit4::class)
36+
class JetnewsUiTest {
37+
38+
@get:Rule
39+
val composeTestRule = JetnewsComposeTestRule()
40+
41+
@Before
42+
fun setUp() {
43+
composeTestRule.launchJetNewsApp()
44+
}
45+
46+
@Test
47+
fun app_launches() {
48+
findByText("Jetnews").assertIsDisplayed()
49+
}
50+
51+
@Test
52+
fun app_opensArticle() {
53+
findAllByText("Manuel Vivo").first().doClick()
54+
workForComposeToBeIdle()
55+
findByText("July 30 • 3 min read").assertIsDisplayed()
56+
57+
// Temporary workaround - needs to go back since Activities don't start from scratch
58+
goBack()
59+
}
60+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.example.jetnews.util
2+
3+
import android.util.DisplayMetrics
4+
import android.view.ViewGroup
5+
import android.view.ViewTreeObserver
6+
import androidx.compose.Composable
7+
import androidx.test.rule.ActivityTestRule
8+
import androidx.ui.core.Density
9+
import androidx.ui.core.setContent
10+
import androidx.ui.test.ComposeTestCase
11+
import androidx.ui.test.ComposeTestCaseSetup
12+
import androidx.ui.test.ComposeTestRule
13+
import androidx.ui.test.android.AndroidComposeTestCaseSetup
14+
import com.example.jetnews.ui.MainActivity
15+
import org.junit.runner.Description
16+
import org.junit.runners.model.Statement
17+
import java.util.concurrent.CountDownLatch
18+
import java.util.concurrent.TimeUnit
19+
20+
/**
21+
* Jetnews specific implementation of [ComposeTestRule].
22+
*/
23+
class JetnewsComposeTestRule : ComposeTestRule {
24+
25+
val activityTestRule = ActivityTestRule<MainActivity>(MainActivity::class.java)
26+
27+
override val density: Density get() = Density(activityTestRule.activity)
28+
29+
override val displayMetrics: DisplayMetrics
30+
get() =
31+
activityTestRule.activity.resources.displayMetrics
32+
33+
override fun apply(base: Statement, description: Description?): Statement {
34+
return activityTestRule.apply(AndroidComposeStatement(base), description)
35+
}
36+
37+
override fun runOnUiThread(action: () -> Unit) {
38+
// Workaround for lambda bug in IR
39+
activityTestRule.activity.runOnUiThread(object : Runnable {
40+
override fun run() {
41+
action.invoke()
42+
}
43+
})
44+
}
45+
46+
/**
47+
* Use this in your tests to setup the UI content to be tested. This should be called exactly
48+
* once per test.
49+
*/
50+
@SuppressWarnings("SyntheticAccessor")
51+
override fun setContent(composable: @Composable() () -> Unit) {
52+
val drawLatch = CountDownLatch(1)
53+
val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
54+
override fun onGlobalLayout() {
55+
drawLatch.countDown()
56+
val contentViewGroup =
57+
activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
58+
contentViewGroup.viewTreeObserver.removeOnGlobalLayoutListener(this)
59+
}
60+
}
61+
val runnable: Runnable = object : Runnable {
62+
override fun run() {
63+
activityTestRule.activity.setContent(composable)
64+
val contentViewGroup =
65+
activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
66+
contentViewGroup.viewTreeObserver.addOnGlobalLayoutListener(listener)
67+
}
68+
}
69+
activityTestRule.runOnUiThread(runnable)
70+
drawLatch.await(1, TimeUnit.SECONDS)
71+
}
72+
73+
override fun forGivenContent(composable: @Composable() () -> Unit): ComposeTestCaseSetup {
74+
val testCase = object : ComposeTestCase {
75+
@Composable
76+
override fun emitContent() {
77+
composable()
78+
}
79+
}
80+
return AndroidComposeTestCaseSetup(
81+
this,
82+
testCase,
83+
activityTestRule.activity
84+
)
85+
}
86+
87+
override fun forGivenTestCase(testCase: ComposeTestCase): ComposeTestCaseSetup {
88+
return AndroidComposeTestCaseSetup(
89+
this,
90+
testCase,
91+
activityTestRule.activity
92+
)
93+
}
94+
95+
inner class AndroidComposeStatement(
96+
private val base: Statement
97+
) : Statement() {
98+
override fun evaluate() {
99+
base.evaluate()
100+
}
101+
}
102+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2019 Google, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.jetnews.util
18+
19+
import androidx.compose.Composable
20+
import androidx.ui.core.semantics.getOrNull
21+
import androidx.ui.material.MaterialTheme
22+
import androidx.ui.material.surface.Surface
23+
import androidx.ui.semantics.SemanticsProperties
24+
import androidx.ui.test.ComposeTestRule
25+
import androidx.ui.test.SemanticsNodeInteraction
26+
import androidx.ui.test.doClick
27+
import androidx.ui.test.findAll
28+
import com.example.jetnews.ui.JetnewsApp
29+
30+
fun ComposeTestRule.launchJetNewsApp() {
31+
setContent {
32+
JetnewsApp()
33+
}
34+
}
35+
36+
fun ComposeTestRule.setMaterialContent(children: @Composable() () -> Unit) {
37+
setContent {
38+
MaterialTheme {
39+
Surface(children = children)
40+
}
41+
}
42+
}
43+
44+
/**
45+
* Workarounds, this should be removed when UI testing improves
46+
*/
47+
48+
fun workForComposeToBeIdle() {
49+
// Temporary workaround - use waitForIdle in dev04
50+
Thread.sleep(500)
51+
}
52+
53+
fun goBack() {
54+
// Temporary workaround - need to go back to Home
55+
findEnabled().first().doClick()
56+
}
57+
58+
fun findEnabled(): List<SemanticsNodeInteraction> {
59+
return findAll {
60+
getOrNull(SemanticsProperties.Enabled) == true
61+
}
62+
}
63+
64+
fun findAllByText(text: String, ignoreCase: Boolean = false): List<SemanticsNodeInteraction> {
65+
return findAll {
66+
getOrNull(SemanticsProperties.AccessibilityLabel).equals(text, ignoreCase)
67+
}
68+
}

0 commit comments

Comments
 (0)
Please sign in to comment.