Testing your app is an integral part of app development. By running tests against your app consistently, you can verify your app’s correctness, functional behavior, and usability before you release it publicly.

Testing also provides you with the following advantages:

  • Rapid feedback on failures.
  • Early failure detection in the development cycle.
  • Safer code refactoring, letting you optimize code without worrying about regressions.
  • Stable development velocity, helping you minimize technical debt

In this blog we are going to see the fundamentals of testing, different types of testing and frameworks which make the testing easier.

Level of testing 

  1. Small test – are unit test which validates the app’s behaviour one class at a time
  2. Medium test ( integrated testing ) – are integration tests that validate either interactions between levels of the stack within a module, or interactions between related modules.
  3. Large tests  – are end-to-end tests that validate user journeys spanning multiple modules of your app.

A pyramid containing three layers

Types of testing

  1. Local unit testing
  2. Instrumented unit testing

Local unit testing

  1. Run in our local machine
  2. Functions that are not dependent on any android api can be done in the local unit testing without a real device.

In some cases we may need to access the android api, to achieve this we can use other frameworks like Robolectric, Espresso and Mockito.

Instrumented testing

These are unit tests which rely on interaction with the view and need real devices or emulators to run, which can have access to instrumented information like context of the app. 

It’s best to rely on this method only when it’s essential to evaluate your app’s behavior against actual device hardware.

In the app java folders you can see androidTest and test package.

Test package is where you can write your unit test cases. The test cases in this folder will be running in our local machine

AndroidTest package is the folder where you add all the instrumented test cases, which requires real device or emulator to run.


// for unit testing
testImplementation 'junit:junit:4.13.2'

// For instrumented testing
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'

Unit Test

Create a test class for the class you need to write test cases. Then create a function and annotate it with @Test to make the function testable.

User Assert to validate whether you test cases is passed or failed.

class EmailValidatorTest {

   fun validEmailTestPassed() {
       val email = "user@mail.com"

To run the test case. You can click the green arrow icon on the left side of the class. And select run. Or you can right click inside the class or the file name and select run. You can also run individual test cases.

Instrumented test

Next let’s see how we can write instrumented test cases

Similar to unit test case , create a class in androidTest package.

Annotate the class with @RunWith(AndroidJUnit4::class)

AndroidJUnitRunner is the instrumentation runner. This is essentially the entry point into running your entire suite of tests. It controls the test environment, the test apk, and launches all of the tests defined in your test package. You configure this in your gradle file

android {
    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

AndroidJUnit4 is the class test runner. This is the thing that will drive the tests for a single class. You annotate your test classes with this:

Next we need to start an activity to start the testing. ActivityScenarioRule launches a given activity before the test starts and closes after the test.

@Before is used to execute a function each time before a test function starts and @After is used to execute a function after a test function is completed.

import android.os.Parcel
import android.text.TextUtils.writeToParcel
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

const val TEST_STRING = "This is a string"
const val TEST_LONG = 12345678L

// @RunWith is required only if you use a mix of JUnit3 and JUnit4.
class LogHistoryAndroidUnitTest {
    private lateinit var logHistory: LogHistory

    fun createLogHistory() {
        logHistory = LogHistory()

    fun logHistory_ParcelableWriteRead() {
        val parcel = Parcel.obtain()
        logHistory.apply {
            // Set up the Parcelable object to send and receive.
            addEntry(TEST_STRING, TEST_LONG)

            // Write the data.
            writeToParcel(parcel, describeContents())

        // After you're done with writing, you need to reset the parcel for reading.

        // Read the data.
        val createdFromParcel: LogHistory = LogHistory.CREATOR.createFromParcel(parcel)
        createdFromParcel.getData().also { createdFromParcelData: List<Pair<String, Long>> ->

            // Verify that the received data is correct.

To perform UI actions and testing we can use Espresso library.


Espresso framework provides a way to access the view and perform actions in that view. It helps in keeping the tasks synchronized as we do in real devices.

The main components of Espresso include the following:

  • Espresso – Entry points to interactions with views (via onView() and onData()). Also exposes APIs that are not necessarily tied to any view, such as pressBack().
  • ViewMatchers – A collection of objects that implement the Matcher<? super View> interface. You can pass one or more of these to the onView() method to locate a view within the current view hierarchy.
  • ViewActions – A collection of ViewAction objects that can be passed to the ViewInteraction.perform() method, such as click().
  • ViewAssertions – A collection of ViewAssertion objects that can be passed the ViewInteraction.check() method. Most of the time, you will use the matches assertion, which uses a View matcher to assert the state of the currently selected view.

Test suite :

To organize the execution of your instrumented unit tests, you can group a collection of test classes in a test suite class and run these tests together. Test suites can be nested, your test suite can group other test suites and run all their component test classes together.

class ProductSuite

All you need to do is create a suit class and annotate it with @RunWith(Suite::class) and pass the instrumented test classes inside the @Suite.SuiteClasses() annotation.

To see the real time implementation of testing with hilt checkout the feature blogs Login testing and Product testing

Some of the libraries which can be used while testing are Robolectric and Mockito.

Robolectric Framework

Robolectric is an unit testing framework that simulates the working behaviour of a real device or emulator.

Pros :

  1. Robolectric runs the unit test in our workstation or in a continuous integration environment in a regular JVM without an emulator.
  2. Bcz of this dexing , packaging and installing on emulator steps aren’t necessary, making the cycles of testing very fast.
  3. It handles the inflation of view, loading the resources and other stuff done in the real device.

Cons : 

  1. Device behaviour like sensor and other hardware access is not possible since it requires a real device.


Mockito is an android testing framework that provides a way to mock data objects. This can be useful in cases where the unit test case relies on data from a local db or api.

Mockito provides much apis to use – https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

That’s it, I hope this blog gave you some ideas on writing test cases to your application. This is just a basic introduction on testing. To learn more about testing check out the documentation – https://developer.android.com/training/testing

Happy learning
Team Appmetry

Leave a Reply