Last active
October 5, 2022 11:51
-
-
Save Yougin/05c0c8bb78bf173e6328b6c2dff51483 to your computer and use it in GitHub Desktop.
DSL for UI tests with faked data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** This is a part of my pet project where I use DSL for making UI tests with faked data (swapped at runtime) | |
Back in 2017 we couldn't use spaces in function names :) | |
Inspired by the guru man https://jakewharton.com/testing-robots/ | |
*/ | |
@RunWith(AndroidJUnit4::class) class CatalogsScreenTest { | |
@Inject lateinit var catalogsRobot: CatalogsRobot | |
@Inject lateinit var catalogsRepository: CatalogRepository | |
fun givenCatalogsScreen(func: CatalogsRobot.() -> Unit) = catalogsRobot.apply { func() } | |
@Before fun setup() { | |
val activityTestRule = ActivityTestRule(GodActivity::class.java, false, false) | |
getAppComponent().plus(CatalogsRobotModule(activityTestRule)).inject(this) | |
} | |
@Test fun should_catalogs_be_visible_when_repository_has_any_catalogs() { | |
givenCatalogsScreen { | |
catalogsExist(1) | |
} whenever { | |
doNothing() | |
} assert { | |
listIsShown() | |
} | |
} | |
@Test fun should_adapter_hold_the_same_amount_of_catalogs_as_repository_provides() { | |
val amount = 4 | |
givenCatalogsScreen { | |
catalogsExist(amount) | |
} whenever { | |
doNothing() | |
} assert { | |
adapterHasItems(amount) | |
} | |
} | |
@Test fun should_show_error_message_trying_to_create_new_catalog_which_already_exists() { | |
val coolName = "I exist" | |
givenCatalogsScreen { | |
catalogExistsWith(name = coolName) | |
} whenever { | |
createNewCatalogWith(name = coolName) | |
} assert { | |
catalogAlreadyExistsMessageShown() | |
} | |
} | |
@Test fun should_show_new_catalog_after_adding_it() { | |
givenCatalogsScreen { | |
noPreconditions() | |
} whenever { | |
createNewCatalogWith(name = "new catalog") | |
} assert { | |
adapterHasItems(1) | |
} | |
} | |
@Test fun should_go_to_cards_screen_after_clicking_on_a_catalog() { | |
val newName = "new name" | |
givenCatalogsScreen { | |
catalogExistsWith(name = newName) | |
} whenever { | |
clickOnCatalogWith(name = newName) | |
} assert { | |
navigatedToCardsScreen() | |
} | |
} | |
} | |
class CatalogsRobot(val catalogsRepository: CatalogRepository, | |
val activityTestRule: ActivityTestRule<GodActivity>) { | |
infix fun whenever(func: WhenEver.() -> Unit): WhenEver { | |
activityTestRule.launchActivity(null) | |
return WhenEver().apply { func() } | |
} | |
fun catalogExistsWith(name: String) { | |
catalogRepository().add(name).subscribe() | |
} | |
fun catalogsExist(amount: Int) { | |
(1..amount).forEach { catalogRepository().add(it.toString()).subscribe() } | |
} | |
private fun catalogRepository(): DebugCatalogRepository = | |
catalogsRepository as DebugCatalogRepository | |
inner class WhenEver { | |
val addNewCatalogEditText = R.id.add_new_catalog_edit_text | |
val createNewCatalogButton = R.id.catalogs_create_new_catalog | |
val addCatalogText = R.string.add | |
infix fun assert(func: Assert.() -> Unit) = Assert().apply(func) | |
fun createNewCatalogWith(name: String) { | |
clickOn(createNewCatalogButton) | |
type(text = name, into = addNewCatalogEditText) | |
clickOnText(addCatalogText) | |
} | |
fun clickOnCatalogWith(name: String) { | |
clickOnText(name) | |
} | |
fun doNothing() {} | |
} | |
inner class Assert { | |
val catalogsList = R.id.catalogs_list | |
val catalogExistsText = R.string.error_catalog_exists | |
fun listIsShown() { | |
verifyViewIsShown(catalogsList) | |
} | |
fun adapterHasItems(quantity: Int) { | |
verifyRecyclerViewContains(catalogsList, quantity) | |
} | |
fun catalogAlreadyExistsMessageShown() { | |
verifyToastIsShown(catalogExistsText) | |
} | |
fun navigatedToCardsScreen() { | |
assertThat(getActivity().getCurrentScreen()).isInstanceOf(CardsScreen::class.java) | |
} | |
private fun getActivity() = activityTestRule.activity | |
} | |
fun noPreconditions() {} | |
} | |
// Functions for user actions (all Espresso-related API is encapsulated in these functions, one file.kt). | |
fun clickOn(id: Int) { | |
onView(withId(id)).perform(click()) | |
} | |
fun clickOnText(id: Int) { | |
onView(withText(id)).perform(click()) | |
} | |
fun clickOnText(text: String) { | |
onView(withText(text)).perform(click()) | |
} | |
fun type(text: String, into: Int) { | |
onView(withId(into)).perform(typeTextIntoFocusedView(text)) | |
} | |
// Checks | |
fun verifyViewIsShown(id: Int) { | |
onView(withId(id)).check(matches(isDisplayed())) | |
} | |
fun verifyRecyclerViewContains(id: Int, quantity: Int) { | |
onView(withId(id)).check(RecyclerViewItemCountAssertion(quantity)) | |
} | |
fun verifyRecyclerViewIsNotEmpty(id: Int) { | |
onView(withId(id)).check(RecyclerViewIsNotEmptyAssertion()) | |
} | |
fun verifyToastIsShown(stringId: Int) { | |
onView(withText(stringId)).inRoot(RootMatchers.isPlatformPopup()).check(matches(isDisplayed())) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment