4

I am looking for a mockk equivalent of doReturn(...).when(...).*

I am working on writing some unit tests (testing contracts) that involves a lot of system classes and so need to intercept the methods that I don't control and return some call backs (which the method in code would have eventually returned). In mockito, I could do something like doReturn(...).when(...).*

I Wasn't able to find a similar thing in mockK. Seems like every{} always runs the block before answers or returns.

    class Vehicle: Listener {

    fun displayCar(listener:Listener){
        OtherClass().fetchCar(listener)
    }

    override fun showCarSuccessful() {
        //do something
    }
}

    class OtherClass {
    //assume its an outside function that returns nothing but invokes a method of listener call back
    fun fetchCar(listener: Listener) {
        //... Some system level operations that I don't have control to generate mock objects but in the test I want to use the listener to call some method so that I can
        // test some contracts
        listener.showCarSuccessful()
    }
}

    class Tests {
    @Test
    fun testCarSuccess() {
        val listener: Listener = mockk(relaxed = true)
        val vehicle = Vehicle()
    //also tried with mockkClass and others
        val other: OtherClass = mockk(relaxed = true)
   every { other.fetchCar(listener) } returns {listener.showCarSuccessful()}
   vehicle.displayCar(listener)
//do some verification checks here
    }
}

    interface Listener {
    fun showCarSuccessful()
}
praveen_85
  • 182
  • 2
  • 13
  • `doReturns` in Mockito is DSL hack, semantically it is same as `returns`. Not sure I understand what are you trying to achieve. – oleksiyp Apr 15 '19 at 05:34
  • Thanks for your reply @oleksiyp. I have added some sample code (might have some mistakes) to show what I am trying to do. Not sure if there is a better way to do it and I am new to mockk. So any help is appreciated. Basically what I was expecting is `every { other.fetchCar(listener) } returns listener.showCarSuccessful()` would intercept fetc hCar() and not go through with the implementation but what I see is that the entire method is being processed. – praveen_85 Apr 15 '19 at 06:42
  • There is a fair bit wrong with the example test here, I hope I have deciphered it correctly in my answer. – Laurence Apr 15 '19 at 07:10
  • Thanks @Laurence. I wrote this sample up in a hurry so kind of had some errors in there. I did have every {} block executing before the actual method call. Your example looks good to me. I guess one question that I have is when we say `vehicle.displayCar(listener) verify{ other.fetchCar(listener) }` , wouldn't vehicle.displayCar(listener) run through the original fetchCar method? In my case, I don't want this method to be executed (since it has other non mockable stuff) but rather check that its called and exit from there. I want to return a stub for the fetchCar method. – praveen_85 Apr 15 '19 at 07:34
  • No it would not run through the real implementation - I have injected the Mock. If you want to spy on an actual implementation, you would do this `val otherClassSpy = spyk(OtherClass())` and then it will execute real logic, and you can verify that calls were made. – Laurence Apr 15 '19 at 07:42
  • Ah I see. Understood. Thanks @Laurence for the explanation. – praveen_85 Apr 15 '19 at 07:49

1 Answers1

8

The every{} block is your when clause. You can set up multiple conditions for returning different results. See the example of setting up fixed returns and performing progrommatic answers

import io.mockk.MockKException
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

class MyClass {

    fun add(operand1: Int, operand2: Int): Int {
        TODO()
    }
}

class MockkTest {

    @Test
    fun testMocking() {

        val myClassMock = mockk<MyClass> {
            every { add(1, 2) } returns 3 // Set behaviour
            every { add(2, 2) } returns 4 // Set behaviour
            every { add(3, 4)} answers {args[0] as Int * args[1] as Int} // Programmatic behaviour
        }

        Assertions.assertEquals(3, myClassMock.add(1, 2))
        Assertions.assertEquals(4, myClassMock.add(2, 2))
        Assertions.assertEquals(12, myClassMock.add(3, 4))

        Assertions.assertThrows(MockKException::class.java) {
            myClassMock.add(5, 6) // This behaviour has not been set up.
        }
    }
}

But, in your example in particular, I find this line:

every { other.fetchCar(listener) } returns listener.showCarSuccessful()

very strange. First it is not doing what you think it is doing - it is going to make that call as you set this behaviour up you are telling your mock to return the result of that call, not to do that cal. To do what you want to do, you should rather do this:

every { other.fetchCar(listener) } answers {listener.showCarSuccessful()}

But even then, this line is setting up the mock behaviour after you have called your class under test - set up your mock behaviour first.

Also, it is strange that you are setting up side effects in a top level mock in a nested mock. Surely for testing your Vehicle class all you want to do is verify that its inner class was called with the correct arguments. Also, how does Vehicle get a reference to your OtherClass mock, it is instantiating a new one and calling that function.

Here is an attempt to make your example work:

import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Test

interface Listener {
    fun showCarSuccessful()
}

class Vehicle(val other: OtherClass) : Listener {

    fun displayCar(listener: Listener) {
        other.fetchCar(listener)
    }

    override fun showCarSuccessful() {
        //do something
    }
}


class OtherClass {
    //assume its an outside function that returns nothing but invokes a method of listener call back
    fun fetchCar(listener: Listener) {

    }

}

class VehicleTest{

    @Test
    fun testDisplayCar(){
        val listener: Listener = mockk(relaxed = true)
        val other: OtherClass = mockk(relaxed = true) //also tried with mockkClass and others
        val vehicle = Vehicle(other)

        vehicle.displayCar(listener)

        verify{ other.fetchCar(listener) }
    }
}

Even this I think is maybe still a bit off - I suspect that the listener you want Vehicle to pass to OtherClass is itself, not an argument...

You should also then write a separate test for OtherClass to make sure it does what you expect it to when you call fetchCar

Laurence
  • 1,556
  • 10
  • 13