How to mock and capture this combination of suspend functions

Viewed 141

I have this class and interfaces:

class Class1(
    private val interface1: Interface1,
    private val interface2: Interface2,
    private val interface3: Interface3,
    private val coroutineScope: CoroutineScope
) {

    fun start() {

        coroutineScope.launch {
            val string = interface1.execute { interface2.execute() }
            interface3.execute(string)
        }

    }

}

interface Interface1 {
    suspend fun execute(block: suspend () -> String): String
}

interface Interface2 {
    suspend fun execute(): String
}

interface Interface3 {
    fun execute(string: String)
}

When creating an actual implementation of this all works fine (interface1 is executed, then interface2 is executed, then interface3 is executed):

class MainActivity : AppCompatActivity(), Interface1, Interface2, Interface3 {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val c = Class1(this, this, this, GlobalScope)
        c.start()
    }

    // implements interface1
    override suspend fun execute(block: suspend () -> String): String {
        Log.d("xyz1", "execute interface1")
        return block()
    }

    // implements interface2
    override suspend fun execute(): String {
        Log.d("xyz1", "execute interface2")
        return "someString"
    }

    // implements interface3
    override fun execute(string: String) {
        Log.d("xyz1", "execute interface3 $string")
    }
}

Now I try to test Class1 with the following test:

@ExperimentalCoroutinesApi
class Class1Test {

    private lateinit var interface1: Interface1
    private lateinit var interface2: Interface2
    private lateinit var interface3: Interface3
    private lateinit var tested: Class1

    @Before
    fun setUp() {
        interface1 = mock(Interface1::class.java)
        interface2 = mock(Interface2::class.java)
        interface3 = mock(Interface3::class.java)
        tested = Class1(interface1, interface2, interface3, TestCoroutineScope())
    }

    @Test
    fun `should execute on interface1 then interface2 and then interface3`() = runBlockingTest {
        whenever(interface2.execute()).thenReturn("myString")
        tested.start()

        val captor = argumentCaptor<suspend () -> String>()
        verify(interface1).execute(captor.capture())
        captor.firstValue.invoke()

        then(interface3).should().execute("myString")
    }
}

But interface3.execute() is never called. Debugging it, it seems like string is either not assigned or assigned too late (the lower breakpoint is reached, the upper one not):

Screenshot

Why is this test failing and how do I fix it?

PS: Changes to the production code are not an option. PPS: If you want to play around with it, my little test project can be found here: https://github.com/fmweigl/SuspendTest

0 Answers
Related