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

Spring测试Mockito误用:MissingMethodInvocationException问题及正确用法

核心问题分析
  1. 错误Mock了被测试类:你给MainController标注了@Mock,这意味着调用mainController.healthCheck()时,执行的是Mock对象的空实现,不会走你写的真实业务逻辑,Mock方法默认返回null,这也是引发异常的核心原因之一。
  2. Runtime无法通过常规Mock注入:你的Controller里是直接调用静态方法Runtime.getRuntime()获取实例,不是通过构造器或@Autowired注入的依赖,所以@Mock/@InjectMocks无法替换这个对象——Mockito默认只能处理实例级别的依赖注入,管不到静态方法调用。
  3. Mock使用逻辑混淆:Mock的作用是替换被测试类的依赖组件,而不是被测试类本身。被测试类应该是真实实例,只有它依赖的外部对象才需要Mock。
正确解决方案

方案一:重构Controller(推荐,符合依赖注入原则)

修改Controller,把Runtime通过构造器注入,让依赖可替换:

@RestController
@RequestMapping("/")
public class MainController {
    private final Runtime runtime;

    // 构造器注入Runtime,方便测试替换
    public MainController(Runtime runtime) {
        this.runtime = runtime;
    }

    @GetMapping("/")
    public String initialPage() {
        return "Hello World";
    }

    @GetMapping("/healthCheck")
    public String healthCheck() {
        if (runtime != null) {
            try {
                long maxMemory = runtime.maxMemory();
                long totalMemory = runtime.totalMemory();
                long freeMemory = runtime.freeMemory();
                return "freeMemory = " + freeMemory;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "OK";
    }
}

测试类实现(轻量Mockito测试)

不需要完整Spring上下文,用Mockito扩展直接测试:

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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class MainControllerTest {
    @Mock
    private Runtime runtime; // Mock依赖的Runtime

    @InjectMocks
    private MainController mainController; // 真实Controller实例,自动注入Mock的Runtime

    @Test
    public void testHealthCheck() {
        // 配置Mock的freeMemory返回值
        when(runtime.freeMemory()).thenReturn(25L);

        // 调用真实业务方法
        String result = mainController.healthCheck();

        // 断言结果
        assertEquals("freeMemory = 25", result);
    }
}

测试类实现(Spring Boot上下文测试)

如果需要完整Spring上下文,用@MockBean替换上下文中的Runtime:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@SpringBootTest
public class MainControllerTest {
    @MockBean
    private Runtime runtime;

    @Autowired
    private MainController mainController; // 从上下文获取已注入Mock的真实Controller

    @Test
    public void testHealthCheck() {
        when(runtime.freeMemory()).thenReturn(25L);

        String result = mainController.healthCheck();

        assertEquals("freeMemory = 25", result);
    }
}

方案二:不修改Controller,Mock静态方法(Mockito 3.4+支持)

如果不能修改原代码,可使用Mockito的静态Mock功能,需先添加mockito-inline依赖(Maven示例):

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>

测试类实现

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mockStatic;

@ExtendWith(MockitoExtension.class)
public class MainControllerTest {
    @Test
    public void testHealthCheck() {
        // 静态Mock Runtime类,用try-with-resources自动关闭,避免影响其他测试
        try (MockedStatic<Runtime> mockedRuntime = mockStatic(Runtime.class)) {
            Runtime mockRuntime = org.mockito.Mockito.mock(Runtime.class);
            // 配置getRuntime()返回Mock实例
            mockedRuntime.when(Runtime::getRuntime).thenReturn(mockRuntime);
            // 配置Mock实例的方法返回值
            org.mockito.Mockito.when(mockRuntime.freeMemory()).thenReturn(25L);

            // 创建真实Controller实例
            MainController mainController = new MainController();
            String result = mainController.healthCheck();

            assertEquals("freeMemory = 25", result);
        }
    }
}
关键注意事项
  • 被测试类必须是真实实例,只Mock它的外部依赖。
  • 优先重构代码实现依赖注入,静态Mock是应急方案,会改变全局类行为,需谨慎使用。
  • 使用静态Mock时,一定要用try-with-resources包裹,确保测试后恢复静态方法的原始行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 05:16:07