你如何模拟JavaFX工具包初始化? [英] How do you mock a JavaFX toolkit initialization?

查看:172
本文介绍了你如何模拟JavaFX工具包初始化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

[序言:道歉,这里有很多代码,其中一些代码可能与此问题无关,而一些理解问题所需的代码可能会丢失;请评论,我会相应地编辑问题。]

[preamble: apologies, there is a lot of code here, and some of it may not be relevant to this question while some code which is necessary to understand the problem may be missing; please comment, and I will edit the question accordingly.]

环境:Ubuntu 14.10 x86_64; Oracle JDK 1.8u25。单元测试库是TestNG,版本6.8.13; Mockito是版本1.10.17。

Environment: Ubuntu 14.10 x86_64; Oracle JDK 1.8u25. Unit testing library is TestNG, version 6.8.13; Mockito is version 1.10.17.

在我的GUI应用程序中,JavaFX称之为控制器的东西是非常被动的,因为这个控制器是唯一的东西。 (我称之为显示)确实是发送事件。

In my GUI application, what JavaFX calls a "controller" is pretty passive, in the sense that the only thing that this "controller" (which I call a "display") really does is send events.

现在,当收到需要GUI更新的事件时,它是另一个类,我是调用一个视图,该视图负责更新GUI。简而言之:

Now, when an event is received which requires a GUI update, it is another class, which I call a view, which is responsible for updating the GUI. In short:

显示 - >演示者 - >视图 - >显示

display -> presenter -> view -> display

我有两个单元测试这些:

I have unit tests for two of these:


  • 显示 - >演示者;

  • 演示者 - >视图。

所以,我在这方面已经被覆盖了(我可以改变显示器的优势,这就是我这样做的原因) 。

So, I am pretty much covered on this front (with the advantage that I can change the display, which is why I'm doing it that way).

但现在我尝试测试视图 - >显示部分;我是SOL。

But now I try and test the "view -> display" part; and I am SOL.

作为一个例子,这里是视图类:

As an illustration, here is the view class:

@NonFinalForTesting
public class JavafxTreeTabView
    extends JavafxView<TreeTabPresenter, TreeTabDisplay>
    implements TreeTabView
{
    private final BackgroundTaskRunner taskRunner;

    public JavafxTreeTabView(final BackgroundTaskRunner taskRunner)
        throws IOException
    {
        super("/tabs/treeTab.fxml");
        this.taskRunner = taskRunner;
    }

    JavafxTreeTabView(final BackgroundTaskRunner taskRunner,
        final Node node, final TreeTabDisplay display)
    {
        super(node, display);
        this.taskRunner = taskRunner;
    }


    @Override
    public void loadTree(final ParseNode rootNode)
    {
        taskRunner.compute(() -> buildTree(rootNode), value -> {
            display.parseTree.setRoot(value);
            display.treeExpand.setDisable(false);
        });
    }

    @Override
    public void loadText(final InputBuffer buffer)
    {
        final String text = buffer.extract(0, buffer.length());
        display.inputText.getChildren().setAll(new Text(text));
    }

    @VisibleForTesting
    TreeItem<ParseNode> buildTree(final ParseNode root)
    {
        return buildTree(root, false);
    }

    private TreeItem<ParseNode> buildTree(final ParseNode root,
        final boolean expanded)
    {
        final TreeItem<ParseNode> ret = new TreeItem<>(root);

        addChildren(ret, root, expanded);

        return ret;
    }

    private void addChildren(final TreeItem<ParseNode> item,
        final ParseNode parent, final boolean expanded)
    {
        TreeItem<ParseNode> childItem;
        final List<TreeItem<ParseNode>> childrenItems
            = FXCollections.observableArrayList();

        for (final ParseNode node: parent.getChildren()) {
            childItem = new TreeItem<>(node);
            addChildren(childItem, node, expanded);
            childrenItems.add(childItem);
        }

        item.getChildren().setAll(childrenItems);
        item.setExpanded(expanded);
    }
}

匹配的显示类是:

public class TreeTabDisplay
    extends JavafxDisplay<TreeTabPresenter>
{
    @FXML
    protected Button treeExpand;

    @FXML
    protected TreeView<ParseNode> parseTree;

    @FXML
    protected TextFlow inputText;

    @Override
    public void init()
    {
        parseTree.setCellFactory(param -> new ParseNodeCell(presenter));
    }

    @FXML
    void expandParseTreeEvent(final Event event)
    {
    }

    private static final class ParseNodeCell
        extends TreeCell<ParseNode>
    {
        private ParseNodeCell(final TreeTabPresenter presenter)
        {
            setEditable(false);
            selectedProperty().addListener(new ChangeListener<Boolean>()
            {
                @Override
                public void changed(
                    final ObservableValue<? extends Boolean> observable,
                    final Boolean oldValue, final Boolean newValue)
                {
                    if (!newValue)
                        return;
                    final ParseNode node = getItem();
                    if (node != null)
                        presenter.parseNodeShowEvent(node);
                }
            });
        }

        @Override
        protected void updateItem(final ParseNode item, final boolean empty)
        {
            super.updateItem(item, empty);
            setText(empty ? null : String.format("%s (%s)", item.getRuleName(),
                item.isSuccess() ? "SUCCESS" : "FAILURE"));
        }
    }
}

这是我的测试文件:

public final class JavafxTreeTabViewTest
{
    private final Node node = mock(Node.class);
    private final BackgroundTaskRunner taskRunner = new BackgroundTaskRunner(
        MoreExecutors.newDirectExecutorService(), Runnable::run
    );
    private JavafxTreeTabView view;
    private TreeTabDisplay display;

    @BeforeMethod
    public void init()
        throws IOException
    {
        display = new TreeTabDisplay();
        view = spy(new JavafxTreeTabView(taskRunner, node, display));
    }

    @Test
    public void loadTreeTest()
    {
        final ParseNode rootNode = mock(ParseNode.class);
        final TreeItem<ParseNode> item = mock(TreeItem.class);

        doReturn(item).when(view).buildTree(same(rootNode));

        display.parseTree = mock(TreeView.class);
        display.treeExpand = mock(Button.class);

        view.loadTree(rootNode);


        verify(display.parseTree).setRoot(same(item));
        verify(display.treeExpand).setDisable(false);
    }
}

我预计它会起作用......除此之外没有。然而,相距甚远我试图避开平台代码,即使上面的测试类也失败了,除了这个例外:

I expected it to work... Except that it doesn't. However "far apart" I try to steer away from the platform code, even the test class above fails with this exception:

java.lang.ExceptionInInitializerError
    at sun.reflect.GeneratedSerializationConstructorAccessor5.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
    at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:45)
    at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
    at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:14)
    at org.mockito.internal.creation.cglib.ClassImposterizer.createProxy(ClassImposterizer.java:143)
    at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:58)
    at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:49)
    at org.mockito.internal.creation.cglib.CglibMockMaker.createMock(CglibMockMaker.java:24)
    at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33)
    at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59)
    at org.mockito.Mockito.mock(Mockito.java:1285)
    at org.mockito.Mockito.mock(Mockito.java:1163)
    at com.github.fge.grappa.debugger.csvtrace.tabs.JavafxTreeTabViewTest.loadTreeTest(JavafxTreeTabViewTest.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
    at org.testng.SuiteRunner.run(SuiteRunner.java:254)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:125)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:270)
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:265)
    at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:540)
    at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:502)
    at javafx.scene.control.Control.<clinit>(Control.java:87)
    ... 44 more

所以,简而言之,如何防止上述异常发生?我曾经认为嘲笑小部件就足够了,但显然不是:/看起来我需要嘲笑整个平台环境(因为缺少一个更好的词)但我不知道如何。

So, in short, how do I prevent the exception above from happening? I'd have thought that mocking the widgets away would have been enough, but apparently not :/ It looks like I need to mock the whole "platform context" (for lack of a better word for it) but I have no idea how.

推荐答案

好的,首先要做的事情:我从未在生活中使用过Mockito。但我很好奇,所以我花了几个小时来弄清楚这一点,我想还有很多需要改进的地方。

Ok, first things first: I never used Mockito once in a life. But I was curious, so I spent several hours to figure this out and I guess there is much to improve.

为了让这个工作,我们需要:

So to get this working, we need:


  1. 上述(通过@jewelsea)JUnit线程规则。

  2. 自定义 MockMaker 实现,包装默认 CglibMockMaker

  3. 把东西连在一起。

  1. The aforementioned (by @jewelsea) JUnit Threading Rule.
  2. A custom MockMaker implementation, wrapping the default CglibMockMaker.
  3. Wire the things together.

所以1 + 2就是这样:

So 1+2 is this:

public class JavaFXMockMaker implements MockMaker {

    private final MockMaker wrapped = new CglibMockMaker();
    private boolean jfxIsSetup;

    private void doOnJavaFXThread(Runnable pRun) throws RuntimeException {
        if (!jfxIsSetup) {
            setupJavaFX();
            jfxIsSetup = true;
        }
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        Platform.runLater(() -> {
            pRun.run();
            countDownLatch.countDown();
        });

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    protected void setupJavaFX() throws RuntimeException {
        final CountDownLatch latch = new CountDownLatch(1);
        SwingUtilities.invokeLater(() -> {
            new JFXPanel(); // initializes JavaFX environment
            latch.countDown();
        });

        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
        AtomicReference<T> result = new AtomicReference<>();
        Runnable run = () -> result.set(wrapped.createMock(settings, handler));
        doOnJavaFXThread(run);
        return result.get();
    }

    @Override
    public MockHandler getHandler(Object mock) {
        AtomicReference<MockHandler> result = new AtomicReference<>();
        Runnable run = () -> result.set(wrapped.getHandler(mock));
        doOnJavaFXThread(run);
        return result.get();
    }

    @Override
    public void resetMock(Object mock, MockHandler newHandler, @SuppressWarnings("rawtypes") MockCreationSettings settings) {
        Runnable run = () -> wrapped.resetMock(mock, newHandler, settings); 
        doOnJavaFXThread(run);
    }

}

第3号就在手册之后:


  1. 复制MockMaker的完全限定类名,例如: org.awesome.mockito.JavaFXMockMaker

  2. 创建文件mockito-extensions / org.mockito.plugins.MockMaker。此文件的内容恰好是具有限定名称的一行。

快乐测试&感谢 Andy Till 的线程规则。

Happy testing & kudos to Andy Till for his threading rule.

警告:此实现类型对 MockMaker 使用 CglibMockMaker ,这可能不是你想要的(参见JavaDocs )。

Warning: This implementation kind of hard-codes the MockMaker to use the CglibMockMaker which might not be be what you want in every case (see the JavaDocs).

这篇关于你如何模拟JavaFX工具包初始化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆