Hi folks I'm trying to learn unit tests with Kotlin using Google's github browser sample as a base. My code is very similar indeed but I can't get one of their more basic tests to work (and I don't really understand it).
My main question is what preciesly is the sendResultToUI()
test doing,
@Test
public void sendResultToUI() {
MutableLiveData<Resource<User>> foo = new MutableLiveData<>();
when(userRepository.loadUser("foo")).thenReturn(foo);
Observer<Resource<User>> observer = mock(Observer.class);
userViewModel.getUser().observeForever(observer);
userViewModel.setLogin("foo");
verify(observer, never()).onChanged(any(Resource.class));
User fooUser = TestUtil.createUser("foo");
Resource<User> fooValue = Resource.success(fooUser);
foo.setValue(fooValue);
verify(observer).onChanged(fooValue);
reset(observer);
}
from what I can tell, it says:
- a) When
loadUser("foo")
is called, instead of executing the function just return a new live data guy called foo. - b) Observe the
userViewModel.getUser()
live data - c) Call
setLogin("foo")
, which triggers the live data and callsloadUser("foo")
- d) Verify the observer to
getUser()
we created earlier has never been fired by any instance of a Resource - e) Create a successful foo user, verify that setting it's value triggers the
getUser()
observer
So if all of that is roughly correct, my question is on step d). My code is throwing an exception:
java.lang.IllegalStateException: ArgumentMatchers.any(T::class.java) must not be null
So I guess onChanged
is called with a null value. I really don't get what the hell is going on here - calling setLogin()
in step c) triggers the user switchMap live data which in turn calls userRepositiory.loadUser()
, so that should call the observer to getUser()
yet we ask to verify the opposite (that it's never called). After all calling loadUser() returns foo which we specified in a). Maybe if someone can at least explain the test to me I might make sense of my own code!
Edit: Here is my own current unit test, the classes and models have changed but as far as I can tell the actual code is identical (I'm aware this can probably be more succinct, will worry about that later!)
@Test
fun `send result to UI`(){
val foo = MutableLiveData<Resource<Member>>()
`when`(interactor.callServerLoginRepo(email, password)).thenReturn(foo)
val observer: Observer<Resource<Member>> = mock()
loginViewModel.member.observeForever(observer)
loginViewModel.setLoginCredentials(email, password)
verify<Observer<Resource<Member>>>(observer, never()).onChanged(any(Resource::class.java) as Resource<Member>)
val fooUser = TestUtil.createMember(email)
val fooValue = Resource.success(fooUser)
foo.setValue(fooValue)
verify<Observer<Resource<Member>>>(observer).onChanged(fooValue)
reset<Observer<Resource<Member>>>(observer)
}
MockitoHelpers.kt
fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
The error is also subtly different:
kotlin.TypeCastException: null cannot be cast to non-null type app.core.sdk.data.remote.response.Resource<app.core.sdk.data.model.db.Member>
at app.core.sdk.ui.login.LoginViewModelTest.send result to UI(LoginViewModelTest.kt:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
Edit 2, final code:
So it looks like my main issue here was the confusion over the different any()
functions, basically I need a null safe call to any()
, ideally one that can specify the matching class. The Mockito-Kotlin library looks like the safest route to go for the moment, because I need an any
function which will be fallback to the class I specify and I think his version does so:
inline fun <reified T : Any> any() = Mockito.any(T::class.java) ?: createInstance<T>()
I am assuming the reason the observer is fired with a null value is just something the Mockito.any()
function does, and that's where Kotlin throws the exception.
val foo = MutableLiveData<Resource<Member>>()
//When callServerLoginRepo() is called, return foo live data
`when`(interactor.callServerLoginRepo(email, password)).thenReturn(foo)
//Observe member live data
val observer: Observer<Resource<Member>> = mock()
loginViewModel.member.observeForever(observer)
//Fire setLoginCredentials, and make sure it didn't touch our observed 'member' live data
loginViewModel.setLoginCredentials(email, password)
verify(observer, never()).onChanged(any())
//Create a successful foo user, and set it's value
val fooUser = TestUtil.createMember(email)
val fooValue = Resource.success(fooUser)
foo.value = fooValue
//Ensure setting this did indeed trigger our live data
verify(observer).onChanged(fooValue)
reset(observer)