MockK: Spy, Relaxed Mock, and Partial Mocking [4/5]

In the fourth artcile in a series, we are going to learn what are MockK spies, relaxed mocks and how to do a partial mocking.

At this point, we know quite a lot about mocks, verification, and how to perform stubbings. In this lesson, we are going to expand our horizons and see additional features useful in day-to-day scenarios, like relaxed mocks.

Of course, you can find the rest of the series on my blog, too:

Video Content

As always, if you prefer a video content, then please check out my latest YouTube video:

MockK: Relaxed Mock

What does a relaxed mock mean?

Well, long story short, it is a mock that returns some value for all functions. In other words, the value is returned even if we do not provide a stubbing.

To better understand, let’s take a look at the example:

class Five(
    private val one: FiveOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class FiveOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}

As we can see, the function we want to test invokes two another: returnInt and returnString.

And I am pretty sure that at this point, you know the result without even running the test:

class FiveTest {

    private val one: FiveOne = mockk()
    private val five = Five(one)

    @Test
    fun `should return mocked string`() {
        every { one.returnString() } returns "mocked"

        val result = five.funToTest()

        assertEquals("mocked", result)
    }
}

That’s right, it fails, and MockK complains about missing answer:

no answer found for FiveOne(#1).returnInt() among the configured answers: (FiveOne(#1).returnString()))
io.mockk.MockKException: no answer found for FiveOne(#1).returnInt() among the configured answers: (FiveOne(#1).returnString()))

And as you may have guessed, a relaxed mock may help us here a bit. But how do we define it in MockK?

It’s easy; the only thing we need is to set up the relaxed flag on creation:

private val one: FiveOne = mockk(relaxed = true)

(When working with annotations, we can use @RelaxedMockK instead of @MockK)

So, now, when we repeat our test, it passes!

Moreover, we could even completely skip the stubbing part:

@Test
fun `should return empty string`() {
    val result = five.funToTest()

    assertEquals("", result)
}

But please don’t do that in your tests and treat them as a curiosity😉

And before we head to the next part, I just wanted to mention that for Unit-returning functions, we must set a separate flag- relaxUnitFun – to true:

private val one: FiveOne = mockk(relaxed = true, relaxUnitFun = true)

Partial Mocking

Nextly, let’s focus on the partial mocking.

This technique, on the other hand, allows us to invoke the actual function.

Let’s take a look at the example of a new class to test:

class Six(
    private val one: SixOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class SixOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}

As we can see, apart from the names, it works exactly the same as previously.

So, let’s take a look at the test:

class SixTest {

    private val one: SixOne = mockk()
    private val five = Six(one)

    @Test
    fun `should invoke actual functions`() {
        every { one.returnInt() } answers { callOriginal() }
        every { one.returnString() } answers { callOriginal() }

        val result = five.funToTest()

        assertEquals("Some String", result)
    }
}

As we can see, in MockK, we can use the answers { callOriginal() }  to invoke the actual function. And that’s why we get “Some String” value here.

MockK Spy

As the last step, let’s take a look at the spies. What are they?

Well, they are a mix of real objects with mocks. Whenever we do not specify any stubbing, the actual function is invoked.

So, the main difference between spies and partial mocking is that for a spy, the original function is invoked if we don’t write anything. In partial mocking, we must be explicit.

Let’s take a look at the code to test then. Again, this is a 1:1 copy of previous examples:

class Seven(
    private val one: SevenOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class SevenOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}

Following, let’s implement a simple test with a MockK spy then:

class SevenTest {

    private val one: SevenOne = spyk(SevenOne())

    private val five = Seven(one)

    @Test
    fun `should invoke actual functions`() {
        val result = five.funToTest()

        assertEquals("Some String", result)
    }
}

And we can see the interesting difference here- we pass the instance of the dependent object to the spyk . And although underneath a copy of that object will be used rather than the passed instance, this shows that spies are actual objects with mocking capabilities.

Additionally, we can use a similar notation to mockk:

private val one: SevenOne = spyk()
// or 
private val one = spyk<SevenOne>()

In this case, the default constructor will be invoked.

And if you are wondering when to use spies, and when to choose partial mocks, then I would say that partial mocks will be better whenever actual calls are rare, almost exceptional. If since the very beginning we want to mix actual resposnes with stubs, then spies will be more optiomal option.

Summary

That’s all for the fourth article in a series in which we learned how to deal with MockK spies, relaxed mocks, and partial mocking.

Awesome! And without any further ado, let’s head to the next lesson:

Leave a Reply

Your email address will not be published. Required fields are marked *