issue#14 Write shared test instructionset
This commit is contained in:
parent
8866ac8477
commit
aed9e6bd09
12 changed files with 831 additions and 19 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package org.fnives.test.showcase.testutils.configuration
|
||||
|
||||
import org.junit.rules.TestRule
|
||||
|
||||
interface SnackBarTestRule : TestRule
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue