mockito / mockito

Most popular Mocking framework for unit tests written in Java
http://mockito.org
MIT License
14.82k stars 2.55k forks source link

Cannot spy on Thread in Java 17 #2628

Closed jxblum closed 2 years ago

jxblum commented 2 years ago

Spying on the java.lang.Thread class when running with Java 17 leads to assertions failures.

For example, the following test will fail:

package examples;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.spy;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class ThreadUnitTests {

  @Mock
  private Runnable mockRunnable;

  private Thread newThread(String name) {
    //return new Thread(this.mockRunnable, name);
    return spy(new Thread(this.mockRunnable, name));
  }

  @Test
  public void threadNameIsCorrect() {
    assertThat(newThread("TestThread").getName()).isEqualTo("TestThread");
  }
}

With:

org.junit.ComparisonFailure: 
Expected :"TestThread"
Actual   :null
<Click to see difference>

    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at examples.ThreadUnitTests.threadNameIsCorrect(ThreadUnitTests.java:46)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    ...

Of course, if the test does not "spy" on the Thread, then the test passes.

The test passes with Java 14 and earlier.

Specifically, I am running:

$ java -version
java version "17" 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)

With the latest version of Mockito:

org.mockito:mockito-core:4.5.1
jxblum commented 2 years ago

FYI...

The command-line used to run the test class above is:

/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/java -ea -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA 2021.3.3 CE.app/Contents/lib/idea_rt.jar=60777:/Applications/IntelliJ IDEA 2021.3.3 CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA 2021.3.3 CE.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA 2021.3.3 CE.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA 2021.3.3 CE.app/Contents/plugins/junit/lib/junit-rt.jar:/Users/jblum/cpdev/Codeprimate/workspaces/cp-core-workspace/cp-labs/target/test-classes:/Users/jblum/cpdev/Codeprimate/workspaces/cp-core-workspace/cp-labs/target/classes:/Users/jblum/cpdev/Codeprimate/workspaces/cp-core-workspace/cp-elements/target/classes:/Users/jblum/.m2/repository/org/assertj/assertj-core/3.22.0/assertj-core-3.22.0.jar:/Users/jblum/.m2/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/Users/jblum/.m2/repository/junit/junit/4.13.2/junit-4.13.2.jar:/Users/jblum/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/jblum/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/jblum/.m2/repository/org/mockito/mockito-core/4.5.1/mockito-core-4.5.1.jar:/Users/jblum/.m2/repository/net/bytebuddy/byte-buddy/1.12.9/byte-buddy-1.12.9.jar:/Users/jblum/.m2/repository/net/bytebuddy/byte-buddy-agent/1.12.9/byte-buddy-agent-1.12.9.jar:/Users/jblum/.m2/repository/org/objenesis/objenesis/3.2/objenesis-3.2.jar:/Users/jblum/.m2/repository/edu/umd/cs/mtc/multithreadedtc/1.01/multithreadedtc-1.01.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 examples.ThreadUnitTests
TimvdLippe commented 2 years ago

It should be fixed with using mockito-inline, which we are making the default in the next major version of Mockito: #2589

jxblum commented 2 years ago

Thank you @TimvdLippe! This worked like a charm; the Mockito team rocks!

Also, just to share a bit more feedback. I build my project with both Maven and Gradle, and with Gradle (7.4.2) I did not have this Mockito problem when building with Gradle on Java 17.

I suspect it has to do with the underlying extensions in the Groovy language itself on top of the JVM compared to Java, though I cannot say that for certain. Just found this rather interesting.

Thanks again!

pankajkumarvlink commented 5 months ago

When ever you encounter this Spy related issue, where Spy object is null then we need to pass vm argurment for that kind of package like --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/java.lang=ALL-UNNAMED --add-opens=java.desktop/java.util=ALL-UNNAMED becuase it tries to open that package for Spy, but without this vm args it doesn't allow.