add automated android tests

This commit is contained in:
Mateusz Hinc 2019-12-12 21:23:11 +01:00
parent 8130012710
commit 9fc3c124bf
9 changed files with 351 additions and 3 deletions

View File

@ -25,6 +25,16 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
} }
dependencies { dependencies {
@ -39,9 +49,26 @@ dependencies {
implementation "io.reactivex.rxjava2:rxandroid:2.1.0" implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.2.0' implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
androidTestImplementation 'androidx.test:core-ktx:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion
androidTestImplementation 'androidx.test:rules:' + rootProject.rulesVersion
androidTestImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.espresso:espresso-intents:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.espresso:espresso-contrib:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.ext:truth:' + rootProject.extTruthVersion
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
androidTestImplementation 'org.robolectric:annotations:' + rootProject.robolectricVersion
testImplementation 'androidx.test:core:' + rootProject.coreVersion
testImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
testImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion
testImplementation 'androidx.test.espresso:espresso-intents:' + rootProject.espressoVersion
testImplementation 'androidx.test.espresso:espresso-contrib:' + rootProject.espressoVersion
testImplementation 'androidx.test.ext:truth:' + rootProject.extTruthVersion
testImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
testImplementation 'org.robolectric:robolectric:' + rootProject.robolectricVersion
} }

View File

@ -0,0 +1,82 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.TestUtils.actionOnChild
import pl.edu.amu.wmi.socialaggregator.TestUtils.cleanLocalStorage
import pl.edu.amu.wmi.socialaggregator.activity.AddSocialActivity
@RunWith(AndroidJUnit4::class)
class AddSocialActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(AddSocialActivity::class.java)
@Test
fun initialState_noAddedSocials_oneAvailableSocial() {
cleanLocalStorage(activity)
val newActivity = launchActivity<AddSocialActivity>()
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
}
@Test
fun clickingSocials_movesThemFormAvailableToAdded_etViceVersa() {
cleanLocalStorage(activity)
onView(withId(R.id.addSocialRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
actionOnChild(click(), R.id.socialPlatformImage)
)
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
onView(withId(R.id.availableSocialsRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
actionOnChild(click(), R.id.socialPlatformImage)
)
)
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
}
}

View File

@ -15,10 +15,12 @@ import org.junit.Assert.*
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest { class ExampleInstrumentedTest {
@Test @Test
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
appContext.filesDir.delete()
assertEquals("pl.edu.amu.wmi.socialaggregator", appContext.packageName) assertEquals("pl.edu.amu.wmi.socialaggregator", appContext.packageName)
} }
} }

View File

@ -0,0 +1,38 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.activity.AddSocialActivity
import pl.edu.amu.wmi.socialaggregator.activity.MainActivity
import pl.edu.amu.wmi.socialaggregator.activity.NewPostActivity
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(MainActivity::class.java)
@Test
fun clickAddNewSocial_shouldOpenAddNewSocialActivity() {
onView(withId(R.id.connectedSocialsButton)).perform(click())
intended(hasComponent(AddSocialActivity::class.java.name))
}
@Test
fun clickCreatePost_shouldOpenAddNewNewPostActivity() {
onView(withId(R.id.createPostButton)).perform(click())
intended(hasComponent(NewPostActivity::class.java.name))
}
}

View File

@ -0,0 +1,73 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.*
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.TestUtils.atPosition
import pl.edu.amu.wmi.socialaggregator.TestUtils.cleanLocalStorage
import pl.edu.amu.wmi.socialaggregator.TestUtils.getCurrentActivity
import pl.edu.amu.wmi.socialaggregator.activity.MainActivity
@RunWith(AndroidJUnit4::class)
class NewPostActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(MainActivity::class.java)
@Test
fun clickPublishPost_shouldEndActivity() {
onView(withId(R.id.createPostButton)).perform(click())
closeSoftKeyboard()
onView(withId(R.id.publishPost)).perform(click())
assertTrue(getCurrentActivity()!!::class.java.simpleName == "MainActivity")
}
@Test
fun clickPublishPost_shouldIncreasePostCount() {
cleanLocalStorage(activity)
onView(withId(R.id.connectedSocialsButton)).perform(click())
onView(withId(R.id.addSocialRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
TestUtils.actionOnChild(click(), R.id.socialPlatformImage)
)
)
pressBack()
onView(withId(R.id.createPostButton)).perform(click())
closeSoftKeyboard()
onView(withId(R.id.availableSocials))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
TestUtils.actionOnChild(click(), R.id.chip)
)
)
onView(withId(R.id.publishPost)).perform(click())
onView(withId(R.id.previousPostsRecyclerView)).check(
matches(atPosition(0, hasDescendant(withText("Facebook"))))
)
}
}

View File

@ -0,0 +1,109 @@
package pl.edu.amu.wmi.socialaggregator
import android.app.Activity
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import org.hamcrest.CoreMatchers
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.core.AllOf
import org.junit.runner.RunWith
import java.io.File
object TestUtils {
fun cleanLocalStorage(activity: IntentsTestRule<out Activity>) {
deleteRecursive(activity.activity.filesDir)
}
private fun deleteRecursive(fileOrDirectory: File) {
if (fileOrDirectory.isDirectory())
for (child in fileOrDirectory.listFiles())
deleteRecursive(child)
fileOrDirectory.delete()
}
fun actionOnChild(action: ViewAction, childId: Int): ViewAction? {
return object : ViewAction {
override fun getDescription(): String {
return "Action on child"
}
override fun getConstraints(): Matcher<View> {
return AllOf.allOf(
ViewMatchers.isDisplayed(),
ViewMatchers.isAssignableFrom(View::class.java)
)
}
override fun perform(uiController: UiController?, view: View?) {
view?.let {
val child = it.findViewById<View>(childId)
action.perform(uiController, child)
}
}
}
}
fun atPosition(position: Int, itemMatcher: Matcher<View>): Matcher<View> {
checkNotNull(itemMatcher)
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
return false
return itemMatcher.matches(viewHolder.itemView)
}
}
}
fun atPosition(position: Int, id: Int, itemMatcher: Matcher<View>): Matcher<View> {
checkNotNull(itemMatcher)
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
return false
val view = viewHolder.itemView.findViewById<View>(id)
return itemMatcher.matches(view)
}
}
}
fun getCurrentActivity(): Activity? {
var currentActivity: Activity? = null
getInstrumentation().runOnMainSync {
run {
currentActivity =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED
).elementAtOrNull(0)
}
}
return currentActivity
}
}

View File

@ -17,6 +17,7 @@ class FacebookMock : SocialPlatform {
override fun login(context: Context) { override fun login(context: Context) {
val loginsDir = InternalStorage.getFileOrDir(context, "logins") val loginsDir = InternalStorage.getFileOrDir(context, "logins")
loginsDir?.mkdir()
if (loginsDir != null) { if (loginsDir != null) {
val loginFile = File(loginsDir, "facebook") val loginFile = File(loginsDir, "facebook")
loginFile.createNewFile() loginFile.createNewFile()

View File

@ -26,3 +26,18 @@ allprojects {
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
ext {
buildToolsVersion = "28.0.3"
androidxLibVersion = "1.0.0"
coreVersion = "1.3.0-alpha03"
extJUnitVersion = "1.1.2-alpha03"
runnerVersion = "1.3.0-alpha03"
rulesVersion = "1.3.0-alpha03"
espressoVersion = "3.3.0-alpha03"
extJUnitVersion = "1.1.2-alpha03"
extTruthVersion = "1.3.0-alpha03"
robolectricVersion = "4.3.1"
}

View File

@ -19,3 +19,4 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.enableUnitTestBinaryResources=true