Android App From Scratch Part 5 — Instrumentation Tests
In this tutorial series, I will try to create an RSS Reader app step by step. Through this series I will explain:
- How to use Model-View-Presenter in an Android App
- Implementing must have libraries
- Implementing App Logic
- Creating unit tests with JUnit
- Creating Android Instrumentations tests
- Continuous Integration with Travis-CI
The cost of instrumentation tests is much more than JUnit tests. Because a device/emulator is needed to run instrumentation tests. If there is a network operation, UI tests get more complicated and slower. So try to use JUnit tests instead of instrumentation tests.
I will write instrumentation tests with Espresso and use mock responses to make UI tests faster. Espresso will not wait for the network operations. Also, I will test both success and error cases.
There’s a builtin espresso recorder tool in Android Studio. It works but generates spaghetti code. I use it once before writing tests. Then clean most of the boilerplate code.
Before reading the rest of this post, I highly recommend you to read Jake Wharton’s testing robots.
With the test cases I will verify that:
- ViewPager has an adapter and fragments inside viewPager
- RecyclerView has an adapter and item count is as expected
- SwipeRefreshLayout is working and loading data
- An error message is shown is fetching RSS fails
Test rule is set to prevent activity launch as soon as tests start.
@LargeTest
@RunWith(AndroidJUnit4::class)
class AllTests {
@get:Rule
var rule = ActivityTestRule(MainActivity::class.java,
false,
false)
}
Creating mock repositories with mock data.
class MockMainRepository : MainRepository {
override fun parseFeeds() = FEEDS
companion object {
val FEEDS = listOf(
Feed(0, "title1", MockRssRepository.SUCCESS_URL),
Feed(1, "title2", MockRssRepository.ERROR_URL)
)
}
}
For the first time fetchRss()
method is called reponse contains 5 items. When this method called again it contains 10 items. This behaviour is for testing swipe refresh layout.
class MockRssRepository : RssRepository {
private var lastIndex = 1
private val count = 5
override fun fetchRss(url: String, listener: RssResponseListener) {
when (url) {
SUCCESS_URL -> listener.getResponse(url, RssResponse.success(ITEMS.subList(0, lastIndex++ * count)))
ERROR_URL -> listener.getResponse(url, RssResponse.error(Error.generic()))
}
}
companion object {
const val SUCCESS_URL = "https://github.com"
const val ERROR_URL = "error_url"
val ITEMS = listOf(
RssItem().apply {
title = "title0"
link = SUCCESS_URL
}, ....
)
}
}
I override NetworkModule
to inject mock objects.
class MockNetworkModule : NetworkModule() {
@Provides
@Singleton
override fun provideRssRepository(service: RssService): RssRepository = MockRssRepository()
@Provides
@Singleton
override fun provideMainRepository(app: Application): MainRepository = MockMainRepository()
}
Before tests start mock dependencies are injected.
@Before
fun setup() {
NewsApp.setComponent(DaggerAppComponent.builder()
.appModule(AppModule(NewsApp.instance))
.networkModule(MockNetworkModule())
.build())
val intent = Intent()
rule.launchActivity(intent)
}
Now tests can be run.
@Test
fun loadViewPagerTest() {
news {
checkViewPagerItems(rule)
swipeLeft()
swipeRight()
}
}
Here are the details of checkViewPagerItems()
fun checkViewPagerItems(rule: ActivityTestRule<MainActivity>) {
val viewPager = rule.activity.findViewById(R.id.viewPager) as ViewPager
Assert.assertNotNull(viewPager)
Assert.assertNotNull(viewPager.adapter)
Assert.assertTrue(MockMainRepository.FEEDS.size == viewPager.adapter?.count)
}
Other test cases:
@Test
fun recyclerViewItemsTest() {
news {
checkRecyclerViewItemCount(rule, 5)
checkRecyclerViewItemContent()
}
}
@Test
fun swipeRefreshTest() {
news {
checkRecyclerViewItemCount(rule, 5)
swipeDown()
checkSwipeRefresh(rule)
checkRecyclerViewItemCount(rule, 10)
}
}
@Test
fun loadFailed() {
news {
swipeLeft()
checkErrorTextView()
}
}
You can check my GitHub repo for the full source code:
If you liked the article, please 👏👏👏 so more people can see it! Also, you can follow me on Medium