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

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线程初始化问题。

  1. 添加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>
  1. 修改测试类:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:37:00