JUnit测试触发NullPointerException问题求助
解决JUnit测试JavaFX时的NullPointerException问题
问题描述
我正在为大学作业编写JUnit测试,验证玩家输入数量的逻辑(值小于2或大于4时测试应失败)。最初代码运行正常,但重启IntelliJ后每次运行测试都会报错,就算移除失败测试用例问题依然存在。我试过重装Java、JavaFX,切换VSCode和IntelliJ都没解决。
报错信息
java.lang.NullPointerException: Cannot invoke "com.sun.glass.ui.Timer.resume()" because "this.pulseTimer" is null at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.resumeTimer(QuantumToolkit.java:525) at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit$PulseTask.set(QuantumToolkit.java:213) at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.requestNextPulse(QuantumToolkit.java:887) at javafx.graphics@19-ea/javafx.scene.Parent.markDirtyLayout(Parent.java:939) at javafx.graphics@19-ea/javafx.scene.Parent.requestLayout(Parent.java:963) at javafx.graphics@19-ea/javafx.scene.Scene$8.invalidated(Scene.java:1231) at javafx.base@19-ea/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112) at javafx.base@19-ea/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147) at javafx.graphics@19-ea/javafx.scene.Scene.setRoot(Scene.java:1181) at javafx.graphics@19-ea/javafx.scene.Scene.<init>(Scene.java:359) at javafx.graphics@19-ea/javafx.scene.Scene.<init>(Scene.java:239) at com.cascadia.demo@1.0-SNAPSHOT/com.cascadia.demo.ViewLauncher.<init>(ViewLauncher.java:47) at com.cascadia.demo@1.0-SNAPSHOT/com.cascadia.demo.ViewLauncherTest.testGetNumPlayer(ViewLauncherTest.java:18) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:578) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
当前JUnit测试代码
import javafx.scene.control.TextField; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class ViewLauncherTest { @Test public void testSetNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("3")); Assertions.assertEquals(3, view.getNum_Player()); } @Test public void testGetNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("4")); Assertions.assertEquals(4, view.getNum_Player()); } @Test public void testInvalidNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("5")); Assertions.assertThrows(NumberFormatException.class, () -> view.getNum_Player()); } }
解决方案
问题根源
这个空指针异常是因为JavaFX的UI组件(比如Scene、Stage)必须在JavaFX应用线程中初始化,但JUnit测试默认在普通Java线程中执行,导致JavaFX内部的pulseTimer定时器未正确初始化,进而触发报错。
方案1:分离业务逻辑与UI层(推荐)
把玩家数量验证的逻辑从ViewLauncher中抽离出来,单独做成一个业务类,这样测试时不需要初始化任何JavaFX组件,直接测试纯逻辑代码,既高效又避免线程问题。
示例业务类:
public class PlayerInputValidator { public static int validatePlayerCount(String input) throws NumberFormatException { int count = Integer.parseInt(input); if (count < 2 || count > 4) { throw new NumberFormatException("玩家数量必须在2-4之间"); } return count; } }
对应的测试类:
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class PlayerInputValidatorTest { @Test public void testValidPlayerCount() { Assertions.assertEquals(3, PlayerInputValidator.validatePlayerCount("3")); Assertions.assertEquals(4, PlayerInputValidator.validatePlayerCount("4")); } @Test public void testInvalidPlayerCount() { Assertions.assertThrows(NumberFormatException.class, () -> PlayerInputValidator.validatePlayerCount("1")); Assertions.assertThrows(NumberFormatException.class, () -> PlayerInputValidator.validatePlayerCount("5")); } }
方案2:使用TestFX测试UI逻辑(如需测试UI交互)
如果必须测试UI相关的逻辑,可以用TestFX库,它专门为JavaFX UI测试设计,能自动处理JavaFX线程初始化问题。
- 添加Maven依赖(如果用Gradle对应调整):
<dependency> <groupId>org.testfx</groupId> <artifactId>testfx-core</artifactId> <version>4.0.16-alpha</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testfx</groupId> <artifactId>testfx-junit5</artifactId> <version>4.0.16-alpha</version> <scope>test</scope> </dependency>
- 修改测试类:
import javafx.scene.control.TextField; import javafx.stage.Stage; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.testfx.framework.junit5.ApplicationTest; public class ViewLauncherTest extends ApplicationTest { private ViewLauncher viewLauncher; @Override public void start(Stage stage) throws Exception { viewLauncher = new ViewLauncher(); stage.setScene(viewLauncher.getScene()); // 假设ViewLauncher提供获取Scene的方法 stage.show(); } @Test public void testSetNumPlayer() { viewLauncher.setNum_Player(new TextField("3")); Assertions.assertEquals(3, viewLauncher.getNum_Player()); } @Test public void testGetNumPlayer() { viewLauncher.setNum_Player(new TextField("4")); Assertions.assertEquals(4, viewLauncher.getNum_Player()); } @Test public void testInvalidNumPlayer() { viewLauncher.setNum_Player(new TextField("5")); Assertions.assertThrows(NumberFormatException.class, () -> viewLauncher.getNum_Player()); } }
方案3:临时初始化JavaFX线程(应急用)
如果只是为了快速跑通测试,可以在测试类中手动启动JavaFX平台,但这种方法稳定性差,不推荐长期使用:
import javafx.application.Platform; import javafx.scene.control.TextField; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class ViewLauncherTest { @BeforeAll public static void initJavaFX() { // 启动JavaFX平台 Platform.startup(() -> {}); } @Test public void testSetNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("3")); Assertions.assertEquals(3, view.getNum_Player()); } @Test public void testGetNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("4")); Assertions.assertEquals(4, view.getNum_Player()); } @Test public void testInvalidNumPlayer() { ViewLauncher view = new ViewLauncher(); view.setNum_Player(new TextField("5")); Assertions.assertThrows(NumberFormatException.class, () -> view.getNum_Player()); } }
内容的提问来源于stack exchange,提问作者Bhavyabhatia
相关产品推荐
相关产品推荐

