Instrumental Testing on MLKit (integration test)

23 / Dec / 2022 by janmejay.singh 0 comments

Introduction

Instrumented tests run on Android devices, whether physical or emulated. As such, they can take advantage of the Android framework APIs. Instrumented tests, therefore, provide more fidelity than local tests, though they run much more slowly.

* Image from https://developer.android.com/training/testing/fundamentals

Set up your testing environment

In your app’s top-level build.gradle file, you need to specify these libraries as dependencies:

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

dependencies {
 androidTestImplementation “android.test:runner:$androidXTestVersion”
 androidTestImplementation “androidx.test:rules:$androidXTestVersion”
}

Set up your Folders (test directories)

A typical project in Android Studio contains two directories that hold tests depending on their execution environment. Organize your tests in the following directories as described:

  • (androidTest) : The androidTest directory should contain the tests that run on real or virtual devices. Such tests include integration tests, end-to-end tests, and other tests where the JVM alone cannot validate your app’s functionality.
  • (test) : The test directory should contain the tests that run on your local machine, such as unit tests. In contrast to the above, these can be tests that run on a local JVM.
Test Directories

AndroidJUnit4

Now we will integrate AndroidJUnit4 for instrumental testing.

test runner is a JUnit component that runs tests. Without a test runner, your tests would not run. JUnit provides a default test runner that you get automatically. @RunWith swaps out that default test runner.

The AndroidJUnit4 test runner allows for AndroidX Test to run your test differently depending on whether they are instrumented or local tests.

* Image from https://developer.android.com/codelabs/advanced-android-kotlin-training-testing-basics

Testing has three annotation:

1.@Before : this is where all the setup code goes for setting up components like Viewmodel, Repository, database, application context, etc.

2.@Close : this is the place where we close your resources that we will be using for testing like databases ,files etc.

3.@Test : this is where we write our test that we want to check and assert. All the test statements end with Assert.

@RunWith(AndroidJUnit4::class)
class IntegrationTestMLKit {
 private var mSelectedImage: Bitmap? =link null
 private var image: InputImage? = null
 private lateinit var viewModel: SearchViewModel
 private lateinit var repo: DrugRepository
 private lateinit var db: AppDatabase
 private val drugAPI = RetrofitInstance.api

/**
 * setting up the required classes and instances
 */
 @Before
 fun setup() {
 val appContext = InstrumentationRegistry.getInstrumentation().context
  mSelectedImage = getBitmapFromAssets(appContext, “zyrtec.jpg”)
 image = InputImage.fromBitmap(mSelectedImage!!, 0)
 db = Room.inMemoryDatabaseBuilder(
 ApplicationProvider.getApplicationContext(),
 AppDatabase::class.java).allowMainThreadQueries().build()
 repo = DrugRepository(drugAPI, db)// repo instance
 val dataSource = SearchViewModel(repo)//
 viewModel = dataSource
 }
@After
 fun closeDb() {
 db.close() }
/*
* Function to test the Text scanner
 */
 @Test
 fun testTextScanner() {
 val rawValue: MutableLiveData<String> = MutableLiveData<String>()
 val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
 recognizer.process(image!!)
 .addOnFailureListener { e -> // Task failed with an exception
 e.printStackTrace()
 }
 .addOnCompleteListener {
 if(it.isSuccessful) {
 it.result?.text?.let { itr -> rawValue.value=itr }
 }
 }
 rawValue.getOrAwaitValue()?.let {
 integrateSearchText(it)
 }}

In the above code, we are storing data from TextRecognition in the rawValue Livedata. InstrumentationRegistry.getInstrumentation().context is used to provide the context of the application that all the components can use. rawValue.getOrAwaitValue() : getOrAwaitValue() is a function for live data that waits until the Livedata (rawValue) has a value inside it.

/**
 * checking the scanned text has any response with api, if
 * so we are matching the scanned text with actual drug
 */
 private fun integrateSearchText(text: String) {
 var result: String
 val expected = “cetirizine hydrochloride”
 println(“text $text”)
 viewModel.getapproximateTerm(text)
 viewModel.suggestionList.getOrAwaitValue().let {
 result = it.suggestionGroup.sulinkggestionList.suggestion.toString()
 result = result.drop(1).dropLast(1)
 }
 println(“api $result”)
 assert(result.contains(expected))
 }

In the above function integrateSearchText(text: String), we are checking the response we are getting from suggestionList api call and matching it with the expected result. The assert function is used to check if the test passed or failed.

getOrAwaitValue(): live data extension function

Below is the implementation of getOrAwaitValue() function. You can change getOrAwaitValue as per your requirement.

import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrenapp/build/reports/androidTestst.TimeoutException
/**
this class is created for observing Live data
 */
fun <T> LiveData<T>.getOrAwaitValue() : T{
 var data: T? = null
 val latch = CountDownLatch(1)
 val observer = object : Observer<T> {
 override fun onChanged(t: T) {
 data = t
 this@getOrAwaitValue.removeObserver(this)
 latch.countDown()
 }
 }
 this.observeForever(observer)
 try {
  if (!latch.await(18, TimeUnit.SECONDS)) {
  throw TimeoutException(“Live Data Never gets its value”)
  }
 }
 finally {
 this.removeObserver(observer)
 }
 return data as T
}

This is a fairly complicated method. It creates a Kotlin extension function called getOrAwaitValue which adds an observer, gets the LiveData value, and then cleans up the observer — basically a short, reusable version of the observeForever code shown above. Read the link below for more information link.

Test Result

The results of the tests are stored in the app/build/reports/androidTests folder.

Inside the folder, you will find an index.html file.

Inside index.html you will find a Test Summary of all the tests that you have executed.

Index.html (app/build/reports/androidTests)

Conclusion

Testing an android application was a tough task, but with the help of AndroidJUnit4, we can quickly write test cases for our android application.

Read this link to set up and understand what testing architecture we should follow.

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *