Android UI testing (Instrumentation) with Clean Code Architecture and MVVM

Sreehari Vasudevan
4 min readDec 24, 2019

This article is continuation of Medium Article about Unit Testing , which is covering the first part with necessary basics as well as detailed implementation of Unit testing. Please go through the article for in depth background of Unit Testing (Clean Code + MVVM).

Data Flow — MVVM with Clean Code Arch

As instrumentation testing is targeted to cover UI, this article will concentrate on testing View.🥳

Main Libraries Used

Getting required dependencies👨‍💻

//Mock Web Server
androidTestImplementation "com.squareup.okhttp3:mockwebserver:$XX"
//Koin for UI Testing
androidTestImplementation "org.koin:koin-test:$XX"
//Espresso
androidTestImplementation "androidx.test.espresso:espresso-contrib:$XX"

Detailed dependencies can be found in Github Project

To begin with , save local JSON with expected response in src/androidTest/assets folder as shown below

local json for UI testing

Now we have to create Custom Junit Runner for overriding default runner and configure Application class for required dependencies.

class CustomInstrumentationRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader,
className: String,
context: Context): Application {
return super.newApplication(cl,
TestMainApp::class.java.name,
context)
}
}

Refer the Custom Runner in build.gradle as shown below.

android {
defaultConfig {

//......
testInstrumentationRunner "com.dev.ccodetest.app.CustomInstrumentationRunner"
//......
}
}

This will help in customizing the dependencies required by overriding Application class via the following.

class TestMainApp : MainApp() {
override fun provideDependency() = listOf<Module>()
}

Include Koin dependencies to be used via Application class and specific for Instrumentation testing. This will help us to mock expected responses while testing individual data flow. Refer MainAppInstrumentationDI.kt for the other dependencies.

fun generateTestAppComponent(baseApi: String)
= listOf(
configureNetworkForInstrumentationTest(baseApi),
UseCaseDependency,
MockWebServerInstrumentationTest,
RepoDependency
)

As shown above, pass on the mockserver url to network dependency for testing. Similar to Unit Testing, we will have a base class for UI test cases with required configurations ( BaseUITest.kt )

Create test class and extend the same with BaseUITest. While creating test class, make sure to create under androidTest folder.

Use ActivityTestRule to invoke the respective Activity.

@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(LoginActivity::class.java, true, false)

Inject LoginUseCase and MockWebServer by the following, so that View will have corresponding dependencies while executing.

//Inject login use case created with koin
val mLoginUseCase : LoginUseCase by inject()
//Inject Mockwebserver created with koin
val mMockWebServer : MockWebServer by inject()

Load Koin modules through the following

@Before
fun start(){
super.setUp()
loadKoinModules(generateTestAppComponent
(getMockWebServerUrl()).
toMutableList())
}

We can get required response from MockWebServer and the same can be used in assertions to verify test case result. In the sample test case, invoke the activity and check if RecyclerView’s X element is having Y content or not ! This is done with the help of RecyclerMatcher.kt

val mNameTestOne = "Luke Skywalker"
val mDOBTestOne = "19BBY"
val mNameTestTwo = "Obi-Wan Kenobi"
val mDOBTestTwo = "57BBY"
@Test
fun test_recyclerview_elements_for_expected_response() {
mActivityTestRule.launchActivity(null) //Mocks the network request and get back the json response mockNetworkResponseWithFileContent("success_resp_list.json"
,HttpURLConnection.HTTP_OK)
//....onView(withId(R.id.landingListRecyclerView))
.check(
matches(
recyclerItemAtPosition(
0,
ViewMatchers.hasDescendant(withText(mNameTestOne))
)))
}

In the same lines, we can use scrollToPosition method from espresso-contrib to scroll to the last index. Then we can check if the text matches with expected value or not. Which will be like the following.

onView(withId(R.id.landingListRecyclerView)).perform(
RecyclerViewActions.scrollToPosition<LoginRecyclerViewAdapter.
LoginFragViewHolder>(9))

onView(withId(R.id.landingListRecyclerView))
.check(matches(recyclerItemAtPosition(9
,ViewMatchers.hasDescendant(withText(mNameTestTwo)))))

After running the test case, we can see our test case passed and gave us Green ✔️💚🕺🥳🎉

UI Test case passed.

This concludes both UT and Instrumentation test in Android.

Hope I have given enough input to have a clear picture on the challenging Clean Code Arch + MVVM + UT + UI testing.😉

Do clap 👏 👏 if you find this article helpful.

Thanks for reading. Cheers🥂🤜🤛 . Happy Coding 😀🎉

First Part of the article is over here

Full source code can be found in Github.

All gif images copyright https://giphy.com/

--

--