4

Given this code:

// Subject.kt

open class Subject(var x: Int) {

    constructor(): this(42) {
        println("made it")
    }

    fun doit() {
        x += 1
        println("did it: $x")
    }
}
// Tests.kt

import org.junit.jupiter.api.Test
import org.mockito.Mockito

class Tests {
    @Test
    fun makeit() {
        val mock = Mockito.mock(Subject::class.java)

        val details = Mockito.mockingDetails(mock)
        println("Is mock: ${details.isMock}")
        println("Is spy:  ${details.isSpy}")

        mock.doit()
        mock.doit()
    }
}

When makeit is run, the output is:

Is mock: true
Is spy:  false
did it: 1
did it: 2

This seems to indicate that some instance of the subject is being created but bypassing potentially critical constructor logic. This is consistent with a "partial mock", but the code has done nothing to request such a thing.

I find it surprising this is default behavior since the docs all warn strongly against using partial mocks. I have been unable to find docs that describe when mock() returns a partial mock, and thus can't figure out how to get a "full mock" from a class.

So:

  • When does Mockito.mock() create a partial mock?
  • Can Mockito create a "full mock" for a class? Or just for an interface?
  • How does one request a "full mock"?
Aethon Invictus
  • 275
  • 2
  • 9
  • I would interpret the `return of 1` as a sign that the class has not been mocked. (Check whether you see the `println`.) If it where mocked it is supposed to return `0`. (Unless that is different in kotlin). Partiall mocks are created using `spy`. – second Nov 12 '19 at 22:06
  • I updated the question to show that Mockito reports that the class is mocked. I also added a second call to `doit` to show that the "mock" is holding state. – Aethon Invictus Nov 12 '19 at 23:40
  • 2
    I think this behaviour might be an incompatibility regarding `kotlin`, so I added it as a tag. Did you try other mock libraries (`mockito-kotlin` or `mockk`)? – second Nov 13 '19 at 14:35
  • @second: Doh! We are seeing similar behavior in Java (that is what prompted the investigation). But sure enough, the Java version of the code above creates the expected mock. I will work on a better repro. Thanks! – Aethon Invictus Nov 13 '19 at 21:36
  • 1
    If you have an java example that is causing you problems feel free to add a new question including an [mre]. – second Nov 13 '19 at 22:01

1 Answers1

2

Poking through the source code and by trial and error testing, I have come to the following conclusions:

  1. When mocking a class, Mockito creates an instance of a ByteBuddy-generated subclass of the class without calling the constructor => all member data is default values.
  2. Open methods (the default for Java; declared with open in Kotlin):
    • by default are not called and return the default for the return type.
    • will be called when configured with when(...).thenCallRealMethod().
    • will be called if the mock is created with defaultAnswer set to CALLS_REAL_METHODS
  3. final methods cannot be overridden => they will be invoked normally, but they will see default values for all member data.

So, it seems that all class mocks are partial mocks, but since the default in Java is for methods to be open, they often look like regular mocks. By default, they are effectively regular mocks.

This shows up quickly in Kotlin since methods are final by default.

Knowing how this works makes dealing with class mocks less frustrating!

Aethon Invictus
  • 275
  • 2
  • 9