Android : How To Write A Unit Test For Fragment Depending On A Viewmodel Live Data Attribute?
Solution 1:
First you should separate writing tests for fragment itself and tests for view model and live data.
Since you want to write test for fragment depending on a viewmodel Live data, then I think a solution is to mock the view model (or the repository that view model depends on) and launch your fragment using FragmentScenario and test it. Like what is done in this codelab.
Edit: based on your new provided code
First I do some changes in your code to make it runnable and testable (This code is just a code that runs and is just for testing and isn't a well-formed and well-written code):
MyFragment:
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider.Factory
import androidx.lifecycle.ViewModelProviders
classMyFragment : Fragment() {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)lateinitvar myViewModel: MyViewModel
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)lateinitvar myListView: ListView
overridefunonAttach(context: Context) {
super.onAttach(context)
val FACTORY = object : Factory {
overridefun<T : ViewModel>create(modelClass: Class<T>): T {
return MyViewModel() as T
}
}
myViewModel =
ViewModelProviders.of(this, FACTORY).get(MyViewModel::class.java)
myListView = ListView(context)
myListView.adapter = MyCustomadapter(context, listOf("a", "b", "c"))
}
val items1 = listOf("a", "b", "c")
val items2 = listOf("1", "2")
val items3 = listOf("a1", "a2", "a3")
overridefunonCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
when (myViewModel.status.value) {
"status1" ->
setListContent(items1)
"status2" ->
setListContent(items2)
"status3" ->
setListContent(items3)
else -> setListContent(items1)
}
return View(context)
}
privatefunsetListContent(itemsList: List<String>) {
myListView.adapter = MyCustomadapter(context!!, itemsList)
}
}
MyCustomadapter:
import android.content.Context
import android.database.DataSetObserver
import android.view.View
import android.view.ViewGroup
import android.widget.ListAdapter
classMyCustomadapter(privateval context: Context, privateval itemsList: List<String>) : ListAdapter {
overridefunisEmpty(): Boolean {
return itemsList.isEmpty()
}
overridefungetView(p0: Int, p1: View?, p2: ViewGroup?): View {
return View(context)
}
overridefunregisterDataSetObserver(p0: DataSetObserver?) {
}
overridefungetItemViewType(p0: Int): Int {
return1
}
overridefungetItem(p0: Int): Any {
return itemsList[p0]
}
overridefungetViewTypeCount(): Int {
return3
}
overridefunisEnabled(p0: Int): Boolean {
returntrue
}
overridefungetItemId(p0: Int): Long {
return0
}
overridefunhasStableIds(): Boolean {
returntrue
}
overridefunareAllItemsEnabled(): Boolean {
returntrue
}
overridefununregisterDataSetObserver(p0: DataSetObserver?) {
}
overridefungetCount(): Int {
return itemsList.size
}
}
MyViewModel:
import androidx.lifecycle.MutableLiveDataimport androidx.lifecycle.ViewModelclassMyViewModel : ViewModel() {
var status = MutableLiveData<String>()
}
In the above code, I used the @VisibleForTesting annotation for able to testing private fields. [But I recommend to not do like this, and instead, use public methods or the UI components to test the code behaviour. Since you have not provided any UI component here, I have no other simple choice for testing your code].
Now we add dependencies to app modules' build.gradle:
testImplementation 'junit:junit:4.12'
debugImplementation 'androidx.fragment:fragment-testing:1.1.0'
testImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'androidx.arch.core:core-testing:2.1.0'
junit: is for pure unit testing ['pure' means that you can't use android related code in your junit tests]. We always need this library for writing our android tests.
fragment-testing: for using FragmentScenario. For avoid robolectric style problem we use 'debugImplementation' instead of 'testImplementation'.
androidx.test.ext:junit: is for using AndroidJUnit4 test runner.
robolectric: we use robolectric here for running android instrumentation tests on JVM - locally (instead of running on android emulator or physical device).
androidx.arch.core:core-testing: we use this for testing live data
For able to use android resources in robolectric we need to add a test option to app build.gradle:
android {
...
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
And finally, we write a simple test:
[put this test in "test" source set and not in "androidTest". Also you can create test file for your code by pressing Ctrl + Shift + T in android studio, or by right clicking on class name and pressing generate>Test... and selecting 'test' source set]:
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.fragment.app.testing.launchFragment
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)classMyFragmentTest{
@get:Rulevar instantExecutorRule = InstantTaskExecutorRule()
@TestfunchangingViewModelValue_ShouldSetListViewItems() {
val scenario = launchFragment<MyFragment>()
scenario.onFragment { fragment ->
fragment.myViewModel.status.value = "status1"
assert(fragment.myListView.adapter.getItem(0) == "a")
}
}
}
In the above test, we tested setting list view items by setting live data value. The 'InstantTaskExecutorRule' is for assuring that live data value will be tested in predictable way (As explained here).
If you want to test your UI components (like testing displayed items in screen) with libraries like Espresso or other libraries first add its dependency to gradle and then change the launchFragment<MyFragment>()
to launchFragmentInContainer<MyFragment>()
as described here.
Post a Comment for "Android : How To Write A Unit Test For Fragment Depending On A Viewmodel Live Data Attribute?"