Android: Testing with Robolectric

Android: Testing with Robolectric

Brings fast and reliable unit tests to Android with Robolectric.

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

What is Unit Testing?

Unit Testing is a type of software testing where individual units or components of software are tested. The purpose is to validate that each unit of the software code performs as expected. Unit Testing is done during the development (coding phase) of an application by the developers. Unit Tests isolate a section of code and verify its correctness. A unit may be an individual function, method, procedure, module, or object.

More about Unit Testing

What is Roboletric?

Running tests on an Android emulator or device is slow! Building, deploying, and launching the app often takes a minute or more. That’s no way to do TDD. There must be a better way.

Robolectric is the industry-standard unit testing framework for Android. Robolectric is a framework that brings fast and reliable unit tests to Android. Tests run inside the JVM on your workstation in seconds.

Robolectric provides a JVM-compliant version of the android.jar file. Robolectric handles inflation of views, resource loading, and lots of other stuff that’s implemented in native C code on Android devices.

This enables you to run your Android tests in your continuous integration environment without any additional setup. Roboletric supports resource handling, e.g., inflation of views. You can also use the findViewById() to search in a view.

Roboletric is not an integration test framework, i.e., you cannot test the interaction of Android components with it.

Roboletric does not require additional mocking frameworks, of course, it is still possible to use frameworks like Mockito if desired.

Benefits of Roboletric?

  • Run tests in a simulated Android environment inside a JVM

  • Reduced overhead and flakiness of an emulator

  • Run 10x faster than those on cold-started emulators

  • Supports 17 different versions of Android

Let's simplify Roboletric

  • A testing tool for Android

  • Can test pure Java/Kotlin code

  • Provide the ability to test Android Framework code

  • Don't require a simulator to run the test

  • It uses the host machine to run the tests and which is much faster

Installation

Update your app build.gradle

android {
    android {
        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }
}

dependencies {
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.robolectric:robolectric:4.9'
}

Sync the project and Roboletric is ready for use

First Test

Let's create a class called RoboletricFirstTest under the test package.

@RunWith(RobolectricTestRunner::class)
class RoboletricFirstTest {
    private var conditionToMatch = ""

    @Before
    fun setup() {
        conditionToMatch = "Hello Roboletric!"
    }

    @Test
    fun `our first test to match the greetings`() {
        assert(conditionToMatch == "Hello Roboletric!")
    }

    @After
    fun release() {
        conditionToMatch = ""
    }
}

Run the test (ctrl+shift+F10)and check the output:

✅ Tests passed: 1 out 1 test

Congratulations, we successfully passed our first Roboletric Test.

Things to notice:

  • testImplementation 'org.robolectric:robolectric:4.9' test implementation is applicable for test package only but not for androidTest package. For androidTest package use androidTestImplementation.

  • Test classes must be annotated with @RunWith(RobolectricTestRunner::class)

  • In a class, there might be several tests and to run those tests, we may need to initialize some dependent objects before the test runs. To initialize objects use the annotation @Before in your method.

  • And to release the object when the tests are finished, use the annotation @After in your method.

Practical Testing with Repository

Let's think about practical testing where we might want to test our repository, repository is responsible for making a network call and returning the response. [But for testing purposes, we are using a mock repository implementation]

Create a new repository(interface) under a package called com.rommansabbir.robolectricexample.basictesting

interface AuthRepository {
    fun login(username : String, password : String): String
    fun register(username: String, password: String): Boolean
}

Our auth repository contains two APIs called login and register. In a real project, we will have the implementation of network calls.

Create two new classes called AuthRepositoryTestImpl and AuthRepositoryTest under the package called com. rommansabbir.roboletricexample.basictesting(test package).

class AuthRepositoryTestImpl : AuthRepository {
    override fun login(username: String, password: String): String {
        //make network request here
        return if (username == "rommansabbir" && password == "123456") "login_access" else "Login error"
    }

    override fun register(username: String, password: String): Boolean {
        //make network request here
        return username == "rommansabbir" && password == "123456"
    }
}

Here is our test implementation of auth repository.

@RunWith(RobolectricTestRunner::class)
class AuthRepositoryTest {
    private var authRepo: AuthRepository? = null

    @Before
    fun setup() {
        authRepo = AuthRepositoryTestImpl()
    }

    @Test
    fun `test a valid login`() {
        assert(authRepo?.login("rommansabbir", "123456") == "login_access")
    }

    @Test
    fun `test a invalid login`() {
        assert(authRepo?.login("rommansabbir", "password") == "Login error")
    }

    @Test
    fun `test a valid registration`() {
        assert(authRepo?.register("rommansabbir", "123456") == true)
    }

    @Test
    fun `test a invalid registration`() {
        assert(authRepo?.register("rommansabbir", "password") == false)
    }

    @After
    fun release() {
        authRepo = null
    }
}

Here are our tests for auth repository.

Run the test (ctrl+shift+F10)and check the output:

✅ Tests passed: 4 out 4 test

Application Context-Related Test

In our test, we might need to use the application context to run tests (ex: SharedPreferences usages). Let's create a new class called AppContextTest.

@RunWith(RobolectricTestRunner::class)
class AppContextTest {
    private var context: Application? = null
    private var sharedPreferences: SharedPreferences? = null
    private val key: String = "TEST_KEY"

    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
        sharedPreferences = context?.getSharedPreferences("test", Context.MODE_PRIVATE)
    }

    @Test
    fun `context and shared pref should be not null`() {
        assert(context != null)
        assert(sharedPreferences != null)
    }

    @Test
    fun `test a shared pref writing & get the written object`() {
        var writeDone = false
        sharedPreferences?.edit {
            putBoolean(key, true)
            commit()
            writeDone = true
        }
        assert(writeDone)
        assert(sharedPreferences?.getBoolean(key, false) == writeDone)
    }

    @After
    fun release() {
        sharedPreferences = null
        context = null
    }
}

Here, we are getting the application context by accessing the ApplicationProvider.getApplicationContext() API. We need to add the required dependency testImplementation 'androidx.test:core:1.5.0'

Run the test (ctrl+shift+F10)and check the output:

✅ Tests passed: 2 out 2 test

Activity Testing

Let's test a Activity, where we will test the button name maches to a string (resource) and button click is performed.

@RunWith(RobolectricTestRunner::class)
class MainActivityTest {
    private var mainActivity: MainActivity? = null

    @Before
    fun setup() {
        mainActivity = Robolectric.buildActivity(MainActivity::class.java).create().resume().get()
    }

    @Test
    fun `check activity is initialized properly`() {
        assert(mainActivity != null)
    }

    @Test
    fun `check the button name`() {
        assert(mainActivity?.binding?.mainButton?.text == mainActivity?.getString(R.string.welcome_button))
    }

    @Test
    fun `check button pressed`() {
        var isPressed = false
        mainActivity?.binding?.mainButton?.apply {
            setOnClickListener{
                isPressed = true
            }
            performClick()
        }
        assert(isPressed)
    }

    @Test
    fun release() {
        mainActivity = null
    }
}

Run the test (ctrl+shift+F10)and check the output:

✅ Tests passed: 4 out 4 test

Service Testing

Create a new service called MyService under package com.rommansabbir.robolectricexample.myservice

class MyService : Service() {
    companion object {
        var isServiceInitialized = false
        var isServiceDestroyed = false
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        isServiceInitialized = true
    }

    override fun onDestroy() {
        super.onDestroy()
        isServiceDestroyed = true
    }
}

Create a class called MyServiceTest under package com.rommansabbir.robolectricexample.myservice (test package)

@RunWith(RobolectricTestRunner::class)
class MyServiceTest {
    private var service: MyService? = null

    @Before
    fun setup() {
        service = Robolectric.buildService(MyService::class.java).get()
        MyService.isServiceInitialized = false
        MyService.isServiceDestroyed = false
    }

    @Test
    fun `check if the service is created by calling onCreate method`() {
        service?.onCreate()
        assert(MyService.isServiceInitialized)
    }

    @Test
    fun `check if the service is destroyed by calling onDestroy method`() {
        service?.onDestroy()
        assert(MyService.isServiceDestroyed)
    }

    @After
    fun release() {
        service = null
    }
}

Run the test (ctrl+shift+F10)and check the output:

✅ Tests passed: 2 out 2 test

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

I hope this article will help you to cover basic Android testing with Roboletric.

Project Link (Github): Click here to view the repository

That's it for today. Happy Coding...