0

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?

peanutnut
  • 1
  • 3
  • Hi there. Can you update your question to include the log output, and the relevant dependencies (Kotlin, Mockito, Junit, etc) you've got in your Gradle/Maven file, including the versions. It's important to check the versions are lining up. Also the code you've provided has errors (missing brackets, `void onReady()` isn't Kotlin, classes B/C/D are missing) so it's hard to check - can you update it to make sure it's [reproducible](https://stackoverflow.com/help/minimal-reproducible-example)? – aSemy Sep 13 '21 at 06:17
  • In the test setup you stub `clientProvider.create(...)`, so in `ClassA.createClient()` it's never going to call `getReadyCallback()` - instead Mockito intercepts `clientProvider.create(...)` and will immediately return the mock client. – aSemy Sep 13 '21 at 06:32
  • Sorry I didn't make it clear. Yes it is Java not Kotlin, it's from another package that classA's package depend on. I tried to edit the question. – peanutnut Sep 13 '21 at 13:57

2 Answers2

0

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?

peanutnut
  • 1
  • 3
  • `otherMethod()` will never be called (unless you directly call `onReady()`) because it's wrapped inside a lambda, `ReadyCallback`, and the lambda is ignored and never executed because `clientProvider` is a mock. Try writing the test without mocks and debug step through the code - hopefully that makes it clear. – aSemy Sep 13 '21 at 21:08
0

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)
    }
}
peanutnut
  • 1
  • 3
  • Feel free to mark this as the best answer if you have it solved. – Alex Sep 15 '21 at 04:22
  • It doesn't look like this actually tests your code. It's hard to tell because the code you've provided doesn't compile, but if you change `return ReadyCallback { onReady() }` to `return ReadyCallback { }` then it your tests will still pass. It also looks like `unitUnderTest.createClient()` in test `when create client and ready then call otherMethod` doesn't do anything and can be removed. You need to replace the mocks with actual instances - because you're stubbing methods and so your code isn't executing during a test. – aSemy Sep 18 '21 at 15:53