I'm testing MyFragment
using MockK. The fragment registers an Observer
for a public, mutable LiveData
in the onViewCreated
method. The Observer
calls a method within a mocked ViewModel. However (based on reading log output, in the unit test, verify
appears to run before the Observer
can begin its lambda.
My unit test:
class Tests {
@get:Rule val rule: TestRule = InstantTaskExecutorRule()
//...
@Test
fun updates_vm_with_editingListener() {
/* Given */
val listener = MutableLiveData(false)
fragment.editingListener = listener
mActivity.setFragment(fragment) // <-- this attaches the fragment to a dummy activity
/* When */
listener.value = true
/* Then */
Log.d("MyFragmentTest", "running verify")
verify { vm.instruct(ReadWriteObj) } // vm is a mockk'ed class
}
}
MyFragment contains this code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
editingListener.observe(this, Observer {
Log.d(TAG, "editing changed to $it")
when(it) {
true -> mViewModel.instruct(ReadWriteObj)
false -> mViewModel.instruct(ReadOnlyObj)
}
Log.d(TAG, "editing messaged mViewModel")
})
}
The relevant log output is:
I/TestRunner: started: updates_vm_with_editingListener(FragmentTest)
D/FragmentTest: running verify
D/MyFragment: editing changed to true
D/MyFragment: editing messaged mViewModel
E/TestRunner: failed: updates_vm_with_editingListener(FragmentTest)
E/TestRunner: java.lang.AssertionError: Verification failed: call 1 of 1: MyViewModel(vm#1).instruct(eq(ReadWriteObj@2da49cd))). Only one matching call to LiftGroupCardViewModel(vm#1)/instruct(LiftGroupCardIntent) happened, but arguments are not matching:
And the matched obj with different arguments is a call made in the fragment's init
block and isn't under test here.
I don't understand. The InstantTaskExecutorRule
in my class should make the LiveData
observer trigger immediately when the value is changed rather than in the next cycle/loop thing.
Edit
Shouldn't what actually happens be:
- mock viewmodel created and injected into fragment
- dummy livedata (
listener
) injected into fragment - test begins
- fragment attached to activity
- fragment begins observing
listener
listener
value changes- fragment's observer fires, calling
viewmodel.instruct(ReadWriteObj)
verify
confirmsviewmodel.instruct
was called withReadWriteObj
Edit 2
I know I could insert Thread.sleep(1000)
in my unit test to fix this, but that is a really funky code smell. Right now, there is about 500ms between the verify and the observer behavior, and that's on a laptop from 2013. If this unit test were run, in theory, on some slower machine, the sleep might not fix the problem at all.