mockk: Bug: StackOverflowError in case of complex annotation checks involving HashMap calls

Expected Behavior

Class should be mocked w/o errors

Current Behavior

Stackoverflow Exception stops execution

Failure Information (for bugs)

I try to mockk a class which has a default ctor with several interfaces. The ‘to be mocked’ class itself is a Spring Component (annotated), the interfaces for the ctor are Spring Repositories (annotated). Running the tests produces the mentioned StackOverflowError. Commenting out the mockk<> removes the error.

Steps to Reproduce

Project with Mockk (1.9.3), Spring Boot(2.1.6)/Framework(5.1.8), Spring Mongo(2.1.9) and Junit (4.12).

See code below.

Context

Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.

  • MockK version: 1.9.3
  • OS: Windows 10 1903
  • Kotlin version: 1.3.41
  • JDK version: 1.8
  • JUnit version: 4.12
  • Type of test: unit test

Failure Logs

Stack trace

Exception in thread "main" java.lang.StackOverflowError
	at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
	at io.mockk.proxy.jvm.dispatcher.JvmMockKDispatcher.get(JvmMockKDispatcher.java:16)
	at java.lang.Object.hashCode(Object.java:119)
	at java.util.HashMap.hash(HashMap.java:339)
	at java.util.HashMap.get(HashMap.java:557)
	at sun.reflect.Reflection.filterMethods(Reflection.java:291)
	at java.lang.Class.getMethodHelper(Class.java:1261)
	at java.lang.Class.getMethod(Class.java:1187)
	at java.lang.Object.hashCode(Object.java:119)
	at java.util.HashMap.hash(HashMap.java:339)
	at java.util.HashMap.get(HashMap.java:557)
	at sun.reflect.Reflection.filterMethods(Reflection.java:291)
	at java.lang.Class.getMethodHelper(Class.java:1261)
	at java.lang.Class.getMethod(Class.java:1187)
---- lines repeat a lot -----
        at java.lang.Object.hashCode(Object.java:119)
	at java.util.HashMap.hash(HashMap.java:339)
	at java.util.LinkedHashMap.get(LinkedHashMap.java:440)
	at java.lang.Class.getAnnotation(Class.java:2181)
	at java.lang.Class.isAnnotationPresent(Class.java:2589)
	at org.junit.runner.notification.RunNotifier.wrapIfNotThreadSafe(RunNotifier.java:50)
	at org.junit.runner.notification.RunNotifier.removeListener(RunNotifier.java:42)
	at org.junit.runner.JUnitCore.removeListener(JUnitCore.java:161)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:140)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Exception in thread "Thread-5" java.lang.StackOverflowError
// -----------------------[ YOUR STACK TRACE ENDS HERE ] -----------------------

Minimal reproducible code (the gist of this issue)

// -----------------------[ GRADLE DEFINITIONS ] -----------------------
dependencies {
    implementation(kotlin("stdlib-jdk8"))
    implementation(kotlin("reflect"))
    implementation("org.springframework.boot:spring-boot-starter:2.1.6.RELEASE")
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb:2.1.9.RELEASE")
    
    testImplementation("io.mockk:mockk:1.9.3")
    testImplementation(kotlin("test"))
    testImplementation(kotlin("test-junit"))

    testImplementation("org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE")
    testImplementation("junit:junit:4.12")
}
// -----------------------[ YOUR CODE STARTS HERE ] -----------------------
package io.mockk.gh

import io.mockk.mockk

import org.junit.Test
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository

interface AnotherInterface1
interface AnotherInterface2

class PersistencyClass1
class PersistencyClass2

@Repository
interface Repo1 : MongoRepository<PersistencyClass1, String>, AnotherInterface1

@Repository
interface Repo2 : MongoRepository<PersistencyClass2, String>, AnotherInterface2

@Component
class ToBeMocked(
        private val repo1: Repo1,
        private val repo2: Repo2,
        @Value("\${number:1000}") private val someNumber: Long)

class MockkTest {
    private val mockedObject = mockk<ToBeMocked>() //here it happens

    @Test
    fun dummyTst() {
        //no code needed to reproduce
    }
}
// -----------------------[ YOUR CODE ENDS HERE ] -----------------------

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 18
  • Comments: 17

Commits related to this issue

Most upvoted comments

Ok, so i did some research and tried out some stuff. Turns out it is because of the installed JDK. I used AdoptOpenJDK 1.8.0_212 openJ9 wich leads to the error.

I then tried out some configurations and came up with following list:

AdoptOpenJDK: 1.8.0_212 openJ9 -> FAIL AdoptOpenJDK: 1.8.0_222 hotspot -> OK AdoptOpenJDK: 1.8.0_222 openJ9 -> FAIL AdoptOpenJDK: 11.0.1+13 openJ9 -> OK AdoptOpenJDK: 11.0.4+11 openJ9 -> FAIL AdoptOpenJDK: 11.0.4+11 hotspot -> OK

Overall the hotspot variants are working. I leave it up to Mockk team whether you want to look into supporting openJ9. I switched to hotspot for now.

So long, Felix

Is there any plan to fix this ? I have this same issue with OpenJ9 13

sdkman users should use version 11.0.10.hs-adpt for java 11 support - FYI as this stackoverflow happens in other versions too

Hi, I also facing the same issue when mocking Spring beans by @MockkBean (springmockk). It indeed fail only on openJ9, hotspot is ok. Another observation is that it’s failing only on CGLIB-based proxies. In case of injecting Interface (by default Spring will create JDK proxy) everything is ok.

This is unrelated to Jacoco, this is the minimal reproduction case I could come up with:

  • build.gradle
buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
    }
}

repositories {
    mavenCentral()
}

apply plugin: "kotlin"

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"

    testImplementation "junit:junit:4.13"
    testImplementation "io.mockk:mockk:1.10.0"
}
  • Example.kt
internal class Example 
  • ExampleTest.kt
import io.mockk.mockk
import org.junit.Test

class ExampleTest {
    @Test
    fun test() {
        mockk<Example>(relaxed = true)
    }
}
  • Versions:
$ ./gradlew -version 

------------------------------------------------------------
Gradle 6.5.1
------------------------------------------------------------

Build time:   2020-06-30 06:32:47 UTC
Revision:     66bc713f7169626a7f0134bf452abde51550ea0a

Kotlin:       1.3.72
Groovy:       2.5.11
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          11.0.8 (Eclipse OpenJ9 openj9-0.21.0)
OS:           Mac OS X 10.15.6 x86_64