Spring测试Mockito误用:MissingMethodInvocationException问题及正确用法
核心问题分析
- 错误Mock了被测试类:你给
MainController标注了@Mock,这意味着调用mainController.healthCheck()时,执行的是Mock对象的空实现,不会走你写的真实业务逻辑,Mock方法默认返回null,这也是引发异常的核心原因之一。 - Runtime无法通过常规Mock注入:你的Controller里是直接调用静态方法
Runtime.getRuntime()获取实例,不是通过构造器或@Autowired注入的依赖,所以@Mock/@InjectMocks无法替换这个对象——Mockito默认只能处理实例级别的依赖注入,管不到静态方法调用。 - 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
相关产品推荐
相关产品推荐

