1

EDIT: I've created a ticket with mockito-kotlin here

I have a class defined like so:

package me.jpalacios.poc

class MyClass(
    private val myDependency: MyDependency = MyDependency()
) {
    fun run() {
        myDependency.doSomething()
    }
}
package me.jpalacios.poc

class MyDependency {

    fun doSomething() {
        println("I did something")
    }
}
package me.jpalacios.poc

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.verify

@ExtendWith(MockitoExtension::class)
class MyClassTest {

    @Mock
    private lateinit var myDependency: MyDependency
    @InjectMocks
    private lateinit var myClass: MyClass

    @Test
    fun `Test InjectMocks`() {
        myClass.run()

        verify(myDependency).doSomething()
    }
}

Looks like a test defined like so does not work because the mocks are not injected:

@ExtendWith(MockitoExtension::class)
class MyClassTest {
    @Mock
    private lateinit var dependency: MyDependency
    @InjectMocks
    private lateinit var underTest: MyClass
}
plugins {
    kotlin("jvm") version "1.5.20"
}

group = "me.jpalacios"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))

    testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.2")
    testImplementation("org.assertj:assertj-core:3.20.2")
    testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0")
    testImplementation("org.mockito:mockito-junit-jupiter:3.11.2")
}

tasks{
    jar {
        duplicatesStrategy = DuplicatesStrategy.EXCLUDE

        configurations["compileClasspath"].forEach { file: File ->
            from(zipTree(file.absoluteFile))
        }
    }
    compileKotlin {
        kotlinOptions {
            jvmTarget = "${JavaVersion.VERSION_11}"
        }
    }
    test {
        useJUnitPlatform()
    }
}

Any thoughts as to why?

The output is:

I did something

Wanted but not invoked: myDependency.doSomething(); -> at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) Actually, there were zero interactions with this mock.

Wanted but not invoked: myDependency.doSomething(); -> at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) Actually, there were zero interactions with this mock.

at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) at me.jpalacios.poc.MyClassTest.Test InjectMocks(MyClassTest.kt:22) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ...

user3505258
  • 311
  • 2
  • 9
  • Can you update your answer with the Mockito dependencies and versions you're using please? And any warnings or errors from the output. – aSemy Aug 26 '21 at 07:06
  • 1
    @aSemy added the full code example with output – user3505258 Aug 26 '21 at 08:16
  • I really am no expert in Mockito, but adding default values for constructor arguments literally says that you don't need to be passed a dependency, so I wouldn't be surprised if Mockito doesn't try to inject mocks in this case. It really sees a no-arg constructor available, so why not use it? – Joffrey Aug 26 '21 at 08:50

1 Answers1

0

I really am no expert in Mockito, but adding default values for constructor arguments literally says that you don't need to be passed a dependency, so I wouldn't be surprised if Mockito doesn't try to inject mocks in this case. It really sees a no-arg constructor available, so wouldn't you want it to use it?

In any case, I'm not even sure you need this at all. Why not just instantiate the class under test yourself, and pass it the mock where you need it?

@ExtendWith(MockitoExtension::class)
class MyClassTest {

    @Mock
    private lateinit var myDependency: MyDependency

    @Test
    fun `Test InjectMocks`() {
        MyClass(myDependency).run()

        verify(myDependency).doSomething()
    }
}

If you need to share such initialization among multiple tests, you can use a lazy property, or a @BeforeTest method to create it.

Joffrey
  • 32,348
  • 6
  • 68
  • 100
  • I don't think that's the case @Joffrey. Debugging I can see Mockito trying to call the constructor, the problem is when it inspects the constructor it finds 2 extra arguments than declared in the code – user3505258 Aug 27 '21 at 02:09
  • Which constructor does it try to call? That's my point. The Kotlin code generates 2 constructors in this case, and what I was saying is that mockito chooses the no-arg constructor over the other one. – Joffrey Aug 27 '21 at 07:44
  • if that was the case, it would be breaking the contract of `@InjectMocks` which states: _"Constructor injection; the biggest constructor is chosen"_ See https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/InjectMocks.html – user3505258 Aug 31 '21 at 04:20
  • @user3505258 Ah that's good then, I didn't know about that rule. Still I fail to see why you need InjectMocks at all here – Joffrey Aug 31 '21 at 07:31
  • it's a useful annotation when it works. It's less code in my tests, so I like it. Also, if I'm using it wrong, or it doesn't work with Kotlin in some situations it's something worth knowing – user3505258 Sep 01 '21 at 01:30
  • @user3505258 fair enough. Personally I am usually OK with a couple extra lines of code for a `@Before` method because it means I can actually call the constructor and thus keep the ability to use "Find usages" and refactorings with my IDE. But I get that it's a trade-off – Joffrey Sep 01 '21 at 06:53
  • @user3505258 *when it inspects the constructor it finds 2 extra arguments* - do you know the types of these arguments? Are these extra args the same in your MCVE here? – Joffrey Sep 01 '21 at 06:54
  • 1
    https://stackoverflow.com/questions/53912047/two-additional-types-in-default-constructor-in-kotlin – user3505258 Sep 02 '21 at 07:24