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.
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 fortest
package only but not forandroidTest
package. ForandroidTest
package useandroidTestImplementation
.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...