Android Instrumentation Test with Espresso UI, Koin and Room

Sharing is caring!

Testing, testing, and testing, these words every developer has heard and created the fear behind that. As a developer most of the time we don’t write a unit test, it may be the reason that it required more time to write and execute those. Somehow I agree with this point, and I am okay if the application works very smooth and scalable.

But if the application does not work smoothly or every time bugs generated then people will complain to the developer for application is not tested properly. It doesn’t matter how much time you invest in design and implementation, but mistakes are inevitable, and bugs will appear in the business logic.

Why testing is necessity?

There are many benefits of testing like it gives surety that our application feature working as per implementation and requirement. It also ensures that if we are going to implement any new feature in the application, which will not break my exiting feature etc. Okay, now we got to know there are many more benefits of testing.

As an android developer, we should know what are the testing available to test the feature. Generally, there are two types of testing we can do which are the unit test and instrument test.

Unit Test:

A unit test can be write in app/src/test/java folder to our project, which runs by java JVM. Generally, it does not require any specific android library. The unit test covers the all business logic which we have written for our application feature. It mostly 70% of our codebase to take care.

Integration/InstrumentationTest:

Integration test can be written inside app/src/androidTest/java folder. Android system will execute this test, it means to run this test required the android virtual device called emulator or real device. Generally, it can cover 20% of the codebase of our project.

I am not going to cover the unit testing which means the business logic test in this tutorial. We will see the instrumentation test which is based on the android instrument to execute the test cases.

First of all, we need to add few dependencies into the Gradle file to ensure that that can be perform. Here are the few lists.

   // Core library
    implementation 'androidx.test:core:1.2.0'
    testImplementation "junit:junit:4.13-beta-3"

    // AndroidJUnitRunner and JUnit Rules
    implementation 'androidx.test:runner:1.2.0'
    implementation 'androidx.test:rules:1.2.0'

    // Assertions
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    //espresso
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
    
     // koin
    implementation 'org.koin:koin-test:1.0.2'
    // mock
    implementation 'io.mockk:mockk:1.9.2'

To execute the android instrumentation test file, the system required AndroidTestRunner. I would be recommended to create the custom TestRunner and FakeApplication file which needs to configure with Gradle file.

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

    override fun onCreate() {
        super.onCreate()
    }
}
defaultConfig {
    //....
    testInstrumentationRunner "com.sunil.arch.common.FakeRunner"
}

Okay, now your TestRunner is ready to test the file. Let’s see an example to start testing our room database feature to make sure that working as per our expectation. Now we need to instantiate the Koin module which is required to create the Local database.

private const val DATABASE = "DATABASE"
@RunWith(AndroidJUnit4::class)
abstract class BaseTest: KoinTest {

    protected val database: AppDatabase by inject()

    @Before
    open fun setUp() {
        this.configureDi()
    }

    @After
    open fun tearDown() {
      StandAloneContext.stopKoin()
    }

    // CONFIGURATION
    private fun configureDi() {
        StandAloneContext.startKoin(listOf(configureLocalModuleTest(ApplicationProvider.getApplicationContext<Context>())))
    }

    private fun configureLocalModuleTest(context: Context) = module {
        single(DATABASE) {
            Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
                .allowMainThreadQueries()
                .build()
        }
        factory { (get(DATABASE) as AppDatabase).movieDao() }
    }
}

Now my database module is configured with Koin and now we can apply the database testing. I am going to take an example of inserting fake data into our database table and validate to fetch the same data is inserted corrected or not. It this test passed then we can ensure that database inserting and loading feature is working correctly.

class MovieDaoTest : BaseTest() {

    override fun setUp() {
        super.setUp()
        initDataBase()
    }

    @Test
    fun getMovieData() {
        runBlocking {
            val movie = database.movieDao().getTopRatedMovies()
            Assert.assertEquals(1, movie.size)
        }

    }

    private fun initDataBase() {
        runBlocking {
            database.movieDao().save(DataSet.FAKE_MOVIE)
        }
    }
}

Ok, we need to run this test file by AndroidTestRunner to check wether test case is passed or not. We need to run the below command while connected with the android device or android emulator.

gradlew connectedAndroidTest

It will start executing all the text files associated with androidTest folder. The test report will be generated inside your project directory report folder.

Now let’s see the Espresso UI test. Espresso is a testing framework contained in the Android Testing Support Library. It provides APIs to simulate user interactions and write functional UI tests.

Espresso tests are composed of three major components which are:

  • ViewMatchers: These are a collection of objects used to find the desired view in the current view hierarchy. They are passed to the method to locate and return the desired UI element.
  • ViewActions: They are used to perform actions such as views. They are passed to the ViewInteraction.perform() method.
  • ViewAssertions: They are used to assert the state of the currently selected view. They can be passed to the ViewInteraction.check() method.

In this tutorial, let’s see how can check the ViewMatcher component to ensure that the data is inflating on the correct view of our XML. Please disable all the animation of UI from the Gradle file to interact with testing.

 testOptions {
        animationsDisabled = true
        unitTests.returnDefaultValues = true
    }

While writing the test classes for espresso we need to understand the few annotations which are commonly used in the android system which are described below:

The first thing to notice here are the annotations:

  • @RunWith(AndroidJUnit4.class): Tags the class as an Android JUnit4 class. Espresso unit test should be written as a JUnit 4 test class.
  • @LargeTest: Qualifies a test to run in >2s execution time, makes use of all platform resources, including external communications. Other test qualifiers are @SmallTest and @MediumTest.
  • @Rule: ActivityTestRule and ServiceTestRule are JUnit rules part of the Android Testing Support Library and they provide more flexibility and reduce the boilerplate code required in tests. These rules provide functional testing of a single activity or service respectively.
  • @Test: Each functionality to be tested must be annotated with this. The activity under test will be launched before each of the test annotated with @Test.

Ok, let’s create the first espresso test Class to check the data is inflated on the correct view.

@RunWith(AndroidJUnit4::class)
@LargeTest
class DetailUIUnitTest {

    private lateinit var detailViewModel: DetailViewModel

    @get:Rule
    var mActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java, true, false)


    @Before
    fun beforeTest() {
        mActivityRule.launchActivity(Intent()) // breakpoint here
        detailViewModel = mockk()
    }

    @After
    fun afterTest() {

    }

    @Test
    fun test_data_inflating_on_correct_view() {
        val movie = DataSet.FAKE_MOVIE.first()

        launchFragment(movie)

        Espresso.onView(ViewMatchers.withId(R.id.movie_name)).check(
            ViewAssertions.matches(
                ViewMatchers.withText(
                    CoreMatchers.containsString(movie.title)
                )
            )
        )
}

You can run this file to check whether the test is failed or passed.

09/19 17:04:29: Launching 'DetailUIUnitTest' on samsung SM-J710F.
Running tests

$ adb shell am instrument -w -r -e debug false -e class 'com.sunil.arch.ui.DetailUIUnitTest' com.sunil.arch.test/com.sunil.arch.common.FakeRunner
Waiting for process to come online...
Connected to process 22556 on device 'samsung-sm_j710f-52037884fe916345'.

Started running tests

Wrapping 

Now we have a good understanding of android instrumentation testing. We can play with a few examples of instrumentation testing. You can get the full Github source code of Jetpack Sample. In my next tutorial, we will come with new technical stuff to discuss, ok till then enjoy your healthy day.

If you are wondering to learn Android then Please learn from Android category and wondering to learn Kotlin then Kotlin Category will help you. If you want to learn all the python article, then learn from the python category.

Happy Coding 🙂

0 0 votes
Article Rating

Similar Posts

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ove Stoerholt

Hi!
Is it possible for you to update this article with the most recent version of Koin? (as of this writing v. 2.0.1)