I have a Client class (written in Kotlin in an Android app) that implements an interface ReadyCallback
(written in Java in a library of the app, the app is dependent on this library). In Client I have a createClient()
method which will create a client with the parameter of ReadyCallback. If it's ready, I will perform other tasks by calling classC.otherMethod()
, if not ready, I just create the client without doing other stuff:
In the library:
// Somewhere in this library, I have logic to call `readyCallback.onReady()` when I consider it's "ready"
interface ReadyCallback {
void onReady()
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In the app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test, I want to verify that when I create the client and it's ready, classC's otherMethod() will be invoked. I tried to do the following but it's not correct:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
@Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
@Test
fun `when create client then call otherMethod`() {
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
}
The error message shows:
Wanted but not invoked:
classC.otherMethod();
Actually, there were zero interactions with this mock.
I think the reason I got this error is because, if I don't call getReadyCallback()
, it means I am not invoking the callback, so there's no call to classC.otherMethod(). But other than that I am really stuck on this, I don't know how to unit test my desire behavior (If it's ready, classC.otherMethod() will be called, if not ready, this method won't be called).
I know I can't do things like below because unitUnderTest is not a mock object:
callbackMock = mock()
whenever(unitUnderTest.getReadyCallback()).thenReturn(callbackMock)
whenever(clientProviderMock.create(callbackMock).thenReturn(client)
Can anyone help me out please?
CodePudding user response:
The only way I can think of is to add a boolean flag in callback's onReady()
method. So it will become:
In library:
interface ReadyCallback {
void onReady(final boolean isReady)
}
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady(true);
} else {
readyCallback.onReady(false);
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { isReady -> onReady(isReady) }
}
override fun onReady(isReady: Boolean) {
if (isReady) {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
@Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
@Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady(true)
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
@Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.onReady(false)
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}
But I still don't know how to test without the boolean parameter in the callback's method. Does anyone know how to do that?
CodePudding user response:
I think I figured it out. I don't need a parameter in onReady()
.
In library:
interface ReadyCallback {
void onReady()
}
// place to determine when is "ready"
class Manager {
private final ReadyCallback readyCallback;
public void onConnected(final boolean isConnected) {
if (isConnected) {
readyCallback.onReady();
}
}
}
In app:
class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
fun createClient() {
val client = clientProvider.create(getReadyCallback())
}
private fun getReadyCallback() {
return ReadyCallback { onReady() }
}
override fun onReady() {
logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
classC.otherMethod()
}
}
In unit test:
import com.nhaarman.mockitokotlin2.*
import org.junit.*
class ClassATest {
lateinit var unitUnderTest: ClassA
lateinit var clientProviderMock: ClassB
lateinit var classCMock: ClassC
lateinit var clientMock: ClassD
@Before
override fun setup() {
super.setup()
clientProviderMock = mock()
classCMock = mock()
clientMock = mock()
unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)
whenever(clientProviderMock.create(any()).thenReturn(client)
}
@Test
fun `when create client and ready then call otherMethod`() {
unitUnderTest.onReady()
unitUnderTest.createClient()
verify(classCMock).otherMethod()
}
@Test
fun `when create client and not ready then do not call otherMethod`() {
unitUnderTest.createClient()
verifyZeroInteractions(classCMock)
}
}