When does Mockito.mock create a partial mock instead of a "full" mock?

1k Views Asked by At

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"?
1

There are 1 best solutions below

0
On

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!