You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockito和JUnit模拟静态方法时出现StackOverflowError

解决Mockito模拟Math静态方法导致StackOverflowError的问题

我在练习用JUnit和Mockito测试静态方法时遇到了StackOverflowError,场景是测试一个调用Math.pow的计算器方法。

实现类代码

class LessSimpleCalculatorImp implements LessSimpleCalculator {
    @Override
    public int power(int x, int y) {
        return (int)Math.pow(x, y); // 想验证Math.pow是否被调用
    }
}

测试用例代码

@RunWith(MockitoJUnitRunner.class)
public class LessSimpleCalculator_Test {
    @Test
    public void test_pow() {
        try(MockedStatic<Math> mockedMath = Mockito.mockStatic(Math.class);) {
            mockedMath.when(() -> Math.pow(3, 2)).thenReturn(9.0);
            int result = lessSimpleCalculator.power(3, 2);
            assertEquals(9, result);
            mockedMath.verify(() -> Math.pow(3, 2), times(1));
        }
    }
}

执行mvn clean test时的错误

运行后抛出StackOverflowError,完整堆栈信息如下:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Forking command line: /bin/sh -c cd /home/username/Desktop/jarvis/core_java/jdbc && /usr/lib/jvm/java-18-openjdk-amd64/bin/java -jar /home/username/Desktop/jarvis/core_java/jdbc/target/surefire/surefirebooter12164676474631886113.jar /home/username/Desktop/jarvis/core_java/jdbc/target/surefire/surefire5274553330454463948tmp /home/username/Desktop/jarvis/core_java/jdbc/target/surefire/surefire_015841166298835331040tmp
Running ca.jrvs.exercise.mockito.LessSimpleCalculator_Test
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at ./src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 873
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at ./src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 873
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"
Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.105 s
[INFO] Finished at: 2024-09-19T14:17:59-04:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project jrvs-jdbc-exercise: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ? -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project jrvs-jdbc-exercise: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:215)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)
    at java.lang.reflect.Method.invoke (Method.java:577)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
Caused by: org.apache.maven.plugin.PluginExecutionException: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:148)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)
    at java.lang.reflect.Method.invoke (Method.java:577)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
Caused by: java.lang.RuntimeException: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?
    at org.apache.maven.plugin.surefire.booterclient.output.ForkClient.close (ForkClient.java:257)
    at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork (ForkStarter.java:301)
    at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run (ForkStarter.java:116)
    at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider (AbstractSurefireMojo.java:740)
    at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAllProviders (AbstractSurefireMojo.java:682)
    at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked (AbstractSurefireMojo.java:648)
    at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute (AbstractSurefireMojo.java:586)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)
    at java.lang.reflect.Method.invoke (Method.java:577)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
[ERROR] 
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException

Maven依赖配置

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>5.13.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>

问题原因与修复方案

核心原因

Mockito模拟Math类时,内部实现触发了循环调用:拦截Math.pow时,某些逻辑又调用了真实的Math方法,导致无限递归引发StackOverflowError。同时存在JUnit版本与Mockito依赖不匹配的问题——使用JUnit 4的@RunWith(MockitoJUnitRunner.class),但依赖的是JUnit Jupiter的Mockito包。

修复步骤

  1. 匹配Mockito与JUnit的依赖版本

    • 若继续使用JUnit 4,替换Mockito依赖为mockito-core和mockito-junit4(mockito-junit-jupiter是JUnit 5专用):
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.13.0</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit4</artifactId>
        <version>5.13.0</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
      </dependency>
      
    • 或切换到JUnit 5,用@ExtendWith(MockitoExtension.class)替代@RunWith,保留mockito-junit-jupiter依赖。
  2. 通过包装类隔离JDK静态调用
    直接模拟JDK核心类易引发类加载或递归问题,建议用包装类封装Math.pow:

    • 新增包装类:
      class MathWrapper {
          public double pow(int x, int y) {
              return Math.pow(x, y);
          }
      }
      
    • 修改计算器实现,通过构造函数注入包装类:
      class LessSimpleCalculatorImp implements LessSimpleCalculator {
          private MathWrapper mathWrapper;
      
          public LessSimpleCalculatorImp(MathWrapper mathWrapper) {
              this.mathWrapper = mathWrapper;
          }
      
          @Override
          public int power(int x, int y) {
              return (int)mathWrapper.pow(x, y);
          }
      }
      
    • 测试时模拟MathWrapper而非Math:
      @RunWith(MockitoJUnitRunner.class)
      public class LessSimpleCalculator_Test {
          @Mock
          private MathWrapper mathWrapper;
      
          @InjectMocks
          private LessSimpleCalculatorImp lessSimpleCalculator;
      
          @Test
          public void test_pow() {
              when(mathWrapper.pow(3, 2)).thenReturn(9.0);
              int result = lessSimpleCalculator.power(3, 2);
              assertEquals(9, result);
              verify(mathWrapper, times(1)).pow(3, 2);
          }
      }
      
  3. 升级Maven Surefire插件
    旧版本Surefire插件(2.12.4)与Java 18兼容性差,升级到最新稳定版(如3.2.5):

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
            </plugin>
        </plugins>
    </build>
    

内容的提问来源于stack exchange,提问作者qwerty_99

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:54:50