Skip to content

Instantly share code, notes, and snippets.

@Yougin
Last active October 5, 2022 11:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Yougin/05c0c8bb78bf173e6328b6c2dff51483 to your computer and use it in GitHub Desktop.
Save Yougin/05c0c8bb78bf173e6328b6c2dff51483 to your computer and use it in GitHub Desktop.
DSL for UI tests with faked data
/** 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