issue#14 Write shared test instructionset

This commit is contained in:
Gergely Hegedus 2022-04-06 22:36:35 +03:00
parent 8866ac8477
commit aed9e6bd09
12 changed files with 831 additions and 19 deletions

View file

@ -0,0 +1,181 @@
package org.fnives.test.showcase.endtoend
import android.view.View
import android.view.ViewGroup
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.fnives.test.showcase.R
import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper
import org.fnives.test.showcase.storage.database.DatabaseInitialization
import org.fnives.test.showcase.testutils.idling.CompositeDisposable
import org.fnives.test.showcase.testutils.idling.Disposable
import org.fnives.test.showcase.testutils.idling.IdlingResourceDisposable
import org.fnives.test.showcase.testutils.idling.OkHttp3IdlingResource
import org.fnives.test.showcase.testutils.idling.loopMainThreadFor
import org.fnives.test.showcase.ui.splash.SplashActivity
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsInstanceOf
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
@Ignore("Example for Test Recording")
class LoginLogoutEndToEndTest {
@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(SplashActivity::class.java)
private var disposable: Disposable? = null
@Before
fun before() {
/** Needed to add the dispatcher to the Database */
DatabaseInitialization.dispatcher = UnconfinedTestDispatcher()
/** Needed to register the Okhttp as Idling resource, so Espresso actually waits for the response.*/
val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients()
.associateBy(keySelector = { it.toString() })
.map { (key, client) -> OkHttp3IdlingResource.create(key, client) }
.map(::IdlingResourceDisposable)
disposable = CompositeDisposable(idlingResources)
}
@After
fun after() {
disposable?.dispose()
}
@Test
fun loginLogoutEndToEndTest() {
/** Needed to add looping here so the splash finishes */
loopMainThreadFor(600L)
val textInputEditText = Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.user_edit_text),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.user_input),
0
),
0
),
ViewMatchers.isDisplayed()
)
)
textInputEditText.perform(ViewActions.replaceText("alma"), ViewActions.closeSoftKeyboard())
val textInputEditText2 = Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.password_edit_text),
/** this was too specific and didn't find the element, probably cause the keyboard isn't closed just yet. */
// childAtPosition(
// childAtPosition(
// withId(R.id.password_input),
// 0
// ),
// 0
// ),
// isDisplayed()
)
)
textInputEditText2.perform(ViewActions.replaceText("banan"), ViewActions.closeSoftKeyboard())
val materialButton = Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.login_cta), ViewMatchers.withText("Login"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(android.R.id.content),
0
),
5
),
ViewMatchers.isDisplayed()
)
)
materialButton.perform(ViewActions.click())
val textView = Espresso.onView(
Matchers.allOf(
ViewMatchers.withText("Content"),
ViewMatchers.withParent(
Matchers.allOf(
ViewMatchers.withId(R.id.toolbar),
ViewMatchers.withParent(IsInstanceOf.instanceOf(ViewGroup::class.java))
)
),
ViewMatchers.isDisplayed()
)
)
textView.check(ViewAssertions.matches(ViewMatchers.withText("Content")))
val actionMenuItemView = Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.logout_cta), ViewMatchers.withContentDescription("Logout"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.toolbar),
1
),
0
),
ViewMatchers.isDisplayed()
)
)
actionMenuItemView.perform(ViewActions.click())
val textView2 = Espresso.onView(
Matchers.allOf(
ViewMatchers.withText("Mock Login"),
ViewMatchers.withParent(
Matchers.allOf(
ViewMatchers.withId(R.id.toolbar),
ViewMatchers.withParent(IsInstanceOf.instanceOf(ViewGroup::class.java))
)
),
ViewMatchers.isDisplayed()
)
)
textView2.check(ViewAssertions.matches(ViewMatchers.withText("Mock Login")))
val button = Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.login_cta), ViewMatchers.withText("LOGIN"),
ViewMatchers.withParent(ViewMatchers.withParent(ViewMatchers.withId(android.R.id.content))),
ViewMatchers.isDisplayed()
)
)
button.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}
private fun childAtPosition(parentMatcher: Matcher<View>, position: Int): Matcher<View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Child at position $position in parent ")
parentMatcher.describeTo(description)
}
public override fun matchesSafely(view: View): Boolean {
val parent = view.parent
return parent is ViewGroup && parentMatcher.matches(parent) && view == parent.getChildAt(position)
}
}
}
}

View file

@ -0,0 +1,12 @@
package org.fnives.test.showcase.endtoend
import org.junit.Ignore
import org.junit.runner.RunWith
import org.junit.runners.Suite
@Ignore("Example test Suite")
@RunWith(Suite::class)
@Suite.SuiteClasses(
LoginLogoutEndToEndTest::class,
)
class MyTestSuit

View file

@ -1,12 +0,0 @@
package org.fnives.test.showcase.testutils.configuration
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
interface ServerTypeConfiguration {
val useHttps: Boolean
val url: String
fun invoke(mockServerScenarioSetup: MockServerScenarioSetup)
}

View file

@ -1,5 +0,0 @@
package org.fnives.test.showcase.testutils.configuration
import org.junit.rules.TestRule
interface SnackBarTestRule : TestRule

View file

@ -0,0 +1,51 @@
package org.fnives.test.showcase.ui.login.codekata
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.context.GlobalContext
import org.koin.test.KoinTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
@Ignore("CodeKata")
class CodeKataAuthActivitySharedTest : KoinTest {
@Before
fun setup() {
}
@After
fun tearDown() {
GlobalContext.stopKoin()
}
/** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */
@Test
fun properLoginResultsInNavigationToHome() {
}
/** GIVEN empty password and username WHEN signIn THEN error password is shown */
@Test
fun emptyPasswordShowsProperErrorMessage() {
}
/** GIVEN password and empty username WHEN signIn THEN error username is shown */
@Test
fun emptyUserNameShowsProperErrorMessage() {
}
/** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */
@Test
fun invalidCredentialsGivenShowsProperErrorMessage() {
}
/** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */
@Test
fun networkErrorShowsProperErrorMessage() {
}
}

View file

@ -0,0 +1,70 @@
package org.fnives.test.showcase.ui.login.codekata
import androidx.annotation.StringRes
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.ViewMatchers
import org.fnives.test.showcase.R
import org.fnives.test.showcase.testutils.configuration.SnackbarVerificationHelper
import org.fnives.test.showcase.testutils.viewactions.notIntended
import org.fnives.test.showcase.ui.home.MainActivity
import org.hamcrest.core.IsNot
class CodeKataSharedRobotTest(
private val snackbarVerificationHelper: SnackbarVerificationHelper = SnackbarVerificationHelper()
) {
fun setUsername(username: String): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId(R.id.user_edit_text))
.perform(ViewActions.replaceText(username), ViewActions.closeSoftKeyboard())
}
fun setPassword(password: String): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId(R.id.password_edit_text))
.perform(ViewActions.replaceText(password), ViewActions.closeSoftKeyboard())
}
fun clickOnLogin(): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId(R.id.login_cta))
.perform(ViewActions.click())
}
fun assertPassword(password: String): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId((R.id.password_edit_text)))
.check(ViewAssertions.matches(ViewMatchers.withText(password)))
}
fun assertUsername(username: String): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId((R.id.user_edit_text)))
.check(ViewAssertions.matches(ViewMatchers.withText(username)))
}
fun assertLoadingBeforeRequests(): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId(R.id.loading_indicator))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}
fun assertNotLoading(): CodeKataSharedRobotTest = apply {
Espresso.onView(ViewMatchers.withId(R.id.loading_indicator))
.check(ViewAssertions.matches(IsNot.not(ViewMatchers.isDisplayed())))
}
fun assertErrorIsShown(@StringRes stringResID: Int): CodeKataSharedRobotTest = apply {
snackbarVerificationHelper.assertIsShownWithText(stringResID)
}
fun assertErrorIsNotShown(): CodeKataSharedRobotTest = apply {
snackbarVerificationHelper.assertIsNotShown()
}
fun assertNavigatedToHome(): CodeKataSharedRobotTest = apply {
Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName))
}
fun assertNotNavigatedToHome(): CodeKataSharedRobotTest = apply {
notIntended(IntentMatchers.hasComponent(MainActivity::class.java.canonicalName))
}
}

View file

@ -0,0 +1,16 @@
package org.fnives.test.showcase.ui.login.codekata.rule.dispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@OptIn(ExperimentalCoroutinesApi::class)
class CodeKataMainDispatcherRule : TestRule {
override fun apply(base: Statement, description: Description): Statement =
object : Statement() {
override fun evaluate() {
TODO("Not yet implemented")
}
}
}

View file

@ -0,0 +1,40 @@
package org.fnives.test.showcase.ui.login.codekata.rule.dispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.fnives.test.showcase.storage.database.DatabaseInitialization
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* Sets up the Dispatcher as Main and as the [DatabaseInitialization]'s dispatcher.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class PlainMainDispatcherRule(private val useStandard: Boolean = true) : TestRule {
private var _testDispatcher: TestDispatcher? = null
val testDispatcher
get() = _testDispatcher
?: throw IllegalStateException("TestDispatcher is accessed before it is initialized!")
override fun apply(base: Statement, description: Description): Statement = object : Statement() {
override fun evaluate() {
try {
val dispatcher = if (useStandard) StandardTestDispatcher() else UnconfinedTestDispatcher()
Dispatchers.setMain(dispatcher)
DatabaseInitialization.dispatcher = dispatcher
_testDispatcher = dispatcher
base.evaluate()
} finally {
_testDispatcher = null
Dispatchers.resetMain()
}
}
}
}

View file

@ -0,0 +1,11 @@
package org.fnives.test.showcase.ui.login.codekata.rule.intent
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class CodeKataIntentInitRule : TestRule {
override fun apply(base: Statement, description: Description): Statement {
TODO()
}
}

View file

@ -0,0 +1,25 @@
package org.fnives.test.showcase.ui.login.codekata.rule.intent
import androidx.test.espresso.intent.Intents
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* Takes care of [Intents] initialization.
*/
class PlainIntentInitRule : TestRule {
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
try {
Intents.init()
base.evaluate()
} finally {
Intents.release()
}
}
}
}
}