mockito: Mockito doesn't initialise static constructors before instrumentation correctly

Issue

I seem to have run into a little issue that I assume is not expected behaviour when creating static mocks of a class via the JUnit Jupiter extension.

import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

class PathProtocResolverTest {
  @Mock(answer = Answers.RETURNS_SMART_NULLS)
  MockedStatic<SystemUtils> systemUtilsMock;
  
  @Test
  void undefinedPathThrowsException() {}
}

Upon running this, I get the following exception:

org.mockito.exceptions.base.MockitoException: Cannot instrument class org.apache.commons.lang3.SystemUtils because it or one of its supertypes could not be initialized

	at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:159)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	Suppressed: java.lang.NullPointerException
		at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:190)
		... 2 more
Caused by: java.lang.NumberFormatException: empty String
	at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at java.base/jdk.internal.math.FloatingDecimal.parseFloat(FloatingDecimal.java:122)
	at java.base/java.lang.Float.parseFloat(Float.java:455)
	at org.apache.commons.lang3.JavaVersion.get(JavaVersion.java:304)
	at org.apache.commons.lang3.SystemUtils.<clinit>(SystemUtils.java:407)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:398)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.assureInitialization(InlineBytecodeGenerator.java:236)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:254)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:226)
	at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:108)
	at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:566)
	at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
	at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:202)
	at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:134)
	at org.mockito.Mockito.mockStatic(Mockito.java:2325)
	at org.mockito.internal.configuration.MockAnnotationProcessor.processAnnotationForMock(MockAnnotationProcessor.java:69)
	at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:28)
	at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:25)
	at org.mockito.internal.configuration.IndependentAnnotationEngine.createMockFor(IndependentAnnotationEngine.java:44)
	at org.mockito.internal.configuration.IndependentAnnotationEngine.process(IndependentAnnotationEngine.java:72)
	at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:62)
	at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:47)
	at org.mockito.MockitoAnnotations.openMocks(MockitoAnnotations.java:81)
	at org.mockito.internal.framework.DefaultMockitoSession.<init>(DefaultMockitoSession.java:43)
	at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83)
	... 3 more

Workaround

It appears that Mockito (well, ByteBuddy I guess) is not ensuring that the class being referenced in a MockedStatic is loaded into memory and initialised prior to the tests running (instead relying on implicit side effects to ensure that). I believe this is the case because if I add this block to my test class, then everything passes.

  static {
    // Do some dummy call to eagerly load the class before Mocktio runs.
    SystemUtils.getHostName();
  }

My (albeit very ignorant) guess is that this is to do with the static constructor of the class referencing other static fields after the class has been mocked (and thus discarded the private-scope static fields in the bytecode proxy). I would guess that this could be a likely side effect of how the JUnit plugin works, since unlike calling mock() explicitly, I am assuming the class reference in the generic type variable for MockedStatic is only being resolved to an actual class upon instrumentation.

It is also worth noting the class has to be fully loaded first. Loading the class descriptor only does not resolve this issue: the following still allows the test to fail:

  static {
    // Do some dummy call to eagerly load the class before Mocktio runs.
    assert SystemUtils.class != null;
  }

Platform details

  • org.apache.commons/commons-lang3/3.13.0
  • org.mocktio/mockito-junit-jupiter/5.6.0
  • org.junit.jupiter/junit-jupiter/5.10.0
$ java -version
openjdk version "19.0.1" 2022-10-18
OpenJDK Runtime Environment Corretto-19.0.1.10.1 (build 19.0.1+10-FR)
OpenJDK 64-Bit Server VM Corretto-19.0.1.10.1 (build 19.0.1+10-FR, mixed mode, sharing)

$ uname -oipmsrv
Linux 6.5.6-200.fc38.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Oct  6 19:02:35 UTC 2023 x86_64 unknown unknown GNU/Linux

Since IntelliJ IDEA messes with some of the classpath in odd ways sometimes, I replicated this in IntelliJ IDEA with the following version. Doubt this is overly relevant but I thought I’d include it just in case.

IntelliJ IDEA 2023.2.3 (Community Edition)
Build #IC-232.10072.27, built on October 11, 2023
Runtime version: 17.0.8.1+7-b1000.32 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Linux 6.5.6-200.fc38.x86_64
GC: ZGC Cycles, ZGC Pauses
Memory: 2048M
Cores: 8
Registry:
    debugger.new.tool.window.layout=true
    ide.experimental.ui=true
    ide.balloon.shadow.size=0

Non-Bundled Plugins:
    idea.plugin.protoeditor (232.9559.10)
    com.jetbrains.space (232.10072.29)

Kotlin: 232-1.9.0-IJ10072.27
Current Desktop: GNOME

About this issue

  • Original URL
  • State: open
  • Created 8 months ago
  • Comments: 24 (13 by maintainers)

Commits related to this issue

Most upvoted comments

So what I can see of other implementations of the MockedStatic<T> method is that is used in a try() call, where it makes the mocked static call to the class and when it returns any string it returns that. Could this be a possible solution to your issue?

For reference I have got it from diffblue where they explain how to use it to get the class from the Currency class.

  @Test
  public void testCurrency() {
    Currency usd = Currency.getInstance("USD");
    try (MockedStatic<Currency> mockedStatic = mockStatic(Currency.class)) {
      mockedStatic.when(() -> Currency.getInstance(anyString())).thenReturn(usd);

      Currency eur = Currency.getInstance("EUR");
      assertEquals("USD", eur.getCurrencyCode());
      mockedStatic.verify(() -> Currency.getInstance(anyString()));
    }
  }

This may not be what you are looking for but I think it could possibly be something that might aid with your issue? Or is it that the method MockedStatic should be able to support what you are trying to do?

Hi there, first time contributor here! Wondering if this is something that I could possibly have a go at trying to solve? Let me know!