如何测试JavaFX(MVC)控制器逻辑? [英] How to test JavaFX (MVC) Controller Logic?

查看:843
本文介绍了如何测试JavaFX(MVC)控制器逻辑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们如何正确编写JavaFX Controller逻辑的单元/集成测试?
假设我正在测试的Controller类名为 LoadController ,它的单元测试类是 LoadControllerTest ,我的困惑源于:

How do we properly write unit/integration tests for the JavaFX Controller logic? Assuming the Controller class I'm testing is named LoadController, and it's unit test class is LoadControllerTest, my confusion stems from:


  • 如果 LoadControllerTest 类实例化一个新的 LoadController 对象通过
    LoadController loadController = new LoadController(); 我可以
    然后注入值通过(许多)设置器进入控制器。这似乎是使用反射(遗留代码)的唯一方法。如果我没有将值注入FXML控件,那么控件显然还没有初始化,返回null。

  • If the LoadControllerTest class instantiates a new LoadController object via LoadController loadController = new LoadController(); I can then inject values into the controller via (many) setters. This seems the only way short of using reflection (legacy code). If I don't inject the values into the FXML controls then the controls obviously aren't initialized yet, returning null.

如果我改为使用 FXMLLoader loader.getController()检索 loadController 的方法将正确初始化FXML控件,但
因此调用控制器的 initialize(),这会导致运行速度非常慢,并且因为没有办法注入模拟的依赖项,它更像是一个写得不好的集成测试。

If I instead use FXMLLoader's loader.getController() method to retrieve the loadController it will properly initialize the FXML controls but the controller's initialize() is thus invoked which results in a very slow run, and since there's no way to inject the mocked dependencies, it's more of an integration test poorly written.

我现在正在使用前一种方法,但是在那里更好的方法?

I'm using the former approach right now, but is there a better way?

TestFX

答案这里涉及TestFX,它基于主应用程序的开始 @Tests 方法 Controller类。它显示了一种测试控制器的方法

The answer here involves TestFX which has @Tests based around the main app's start method not the Controller class. It shows a method of testing the controller with

     verifyThat("#email", hasText("test@gmail.com"));

但这个答案涉及 DataFX - 而我只是在询问JavaFX的MVC模式。大多数TestFX的讨论都集中在它的GUI功能上,所以我很好奇它是否也是控制器的理想选择。

but this answer involves DataFX - whereas I'm simply asking about JavaFX's MVC pattern. Most TestFX discussion focuses on it's GUI capabilities, so I'm curious whether it's ideal for the controller too.

以下示例显示了如何使用<$注入控制器c $ c> VBox 以便在测试期间它不为空。有没有更好的办法?请具体说明

The following example shows how I inject the controller with a VBox so that it isn't null during the test. Is there a better way? Please be specific

 public class LoadControllerTest {

    @Rule
    public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();

    private LoadController loadController;
    private FileSorter fileSorter;
    private LocalDB localDB;
    private Notifications notifications;
    private VBox mainVBox = new VBox();      // VBox to inject

    @Before
    public void setUp() throws MalformedURLException {
        fileSorter = mock(FileSorter.class);    // Mock all dependencies    

        when(fileSorter.sortDoc(3)).thenReturn("PDF");   // Expected result

        loadController = new LoadController();
        URL url = new URL("http://example.com/");
        ResourceBundle rb = null;
        loadController.initialize(url, rb);   // Perhaps really dumb approach
    }

    @Test
    public void testFormatCheck() {
        loadController.setMainVBox(mainVBox);  // set value for FXML control
        assertEquals("PDF", loadController.checkFormat(3));
    }
}







public class LoadController implements Initializable {

    @FXML
    private VBox mainVBox;   // control that's null unless injected/instantiated

    private FileSorter fileSorter = new FileSorter();  // dependency to mock

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        //... create listeners
    }

    public String checkFormat(int i) {
        if (mainVBox != null) {    // This is why injection was needed, otherwise it's null
            return fileSorter.sortDoc(i);
        }
        return "";
    }

    public void setMainVBox(VBox menuBar) {
        this.mainVBox = mainVBox;     // set FXML control's value
    }

    // ... many more setters ...
}






UPDATE



这是基于hotzst建议的完整演示,但它返回此错误:


UPDATE

Here's a complete demo based on hotzst's suggestions, but it returns this error:


org.mockito.exceptions.base.MockitoException:无法实例化名为'loadController的
@InjectMocks字段'类型'类com.mypackage.LoadController'。你
没有在字段声明中提供实例,所以我尝试
构造实例。但是构造函数或初始化
块引发了异常:null

org.mockito.exceptions.base.MockitoException: Cannot instantiate @InjectMocks field named 'loadController' of type 'class com.mypackage.LoadController'. You haven't provided the instance at field declaration so I tried to construct the instance. However the constructor or the initialization block threw an exception : null



import javafx.scene.layout.VBox;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class LoadControllerTest {

    @Rule
    public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();
    @Mock
    private FileSorter fileSorter;
    @Mock
    private VBox mainVBox;
    @InjectMocks
    private LoadController loadController;  

    @Test
    public void testTestOnly(){
        loadController.testOnly();    // Doesn't even get this far
    }
}







import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
import java.net.URL;
import java.util.ResourceBundle;

public class LoadController implements Initializable {

    private FileSorter fileSorter = new FileSorter(); // Fails here since creates a real object *not* using the mock.

    @FXML
    private VBox mainVBox;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
      //
    }

    public void testOnly(){
        if(mainVBox==null){
            System.out.println("NULL VBOX");
        }else{
            System.out.println("NON-NULL VBOX"); // I want this to be printed somehow!
        }
    }
}


推荐答案

您可以使用 Mockito 等测试框架在控制器中注入依赖项。因此,您可以放弃大多数设置器,至少是那些仅用于促进测试的设置器。

You can use a test framework like Mockito to inject your dependencies in the controller. Thereby you can forgo probably most of the setters, at least the ones that are only present to facilitate testing.

使用您提供的示例代码我调整了测试类(定义 FileSorter 的内部类):

Going with the example code you provided I adjusted the class under test (define an inner class for the FileSorter):

public class LoadController implements Initializable {

    private FileSorter fileSorter = new FileSorter();

    @FXML
    private VBox mainVBox;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        //
    }

    public void testOnly(){
        if(mainVBox==null){
            System.out.println("NULL VBOX");
        }else{
            System.out.println("NON-NULL VBOX");
        }
    }

    public static class FileSorter {}
}

@FXML 注释在这里没有任何意义,因为没有附加fxml文件,但它似乎没有任何影响代码或测试。

The @FXML annotation does not make any sense here, as no fxml file is attached, but it does not seem to have any effect on the code or Test.

您的测试类可能看起来像这样:

Your test class could then look something like this:

@RunWith(MockitoJUnitRunner.class)
public class LoadControllerTest {

    @Mock
    private LoadController.FileSorter fileSorter;
    @Mock
    private VBox mainVBox;
    @InjectMocks
    private LoadController loadController;

    @Test
    public void testTestOnly(){
        loadController.testOnly();
    }
}

此测试通过以下输出成功运行:

This test runs through successfully with the following output:


NON-NULL VBOX

NON-NULL VBOX

@Rule JavaFXThreadingRule 可以省略,因为在这样的testin中你没有运行应该执行的代码的任何部分在JavaFX线程中。

The @Rule JavaFXThreadingRule can be ommited, as when testin like this you are not running through any part of code that should be executed in the JavaFX Thread.

@Mock 注释以及 MockitoJUnitRunner 创建一个模拟实例,然后将其注入到使用 @InjectMocks 注释的实例中。

The @Mock annotation together with the MockitoJUnitRunner creates a mock instance that is then injected into the instance annotated with @InjectMocks.

一个优秀的教程可以在这里找到。在 EasyMock PowerMock ,但Mockito是我使用的并且最熟悉的。

An excellent tutorial can be found here. There are also other frameworks for mocking in tests like EasyMock and PowerMock, but Mockito is the one I use and am most familiar with.

我使用的是Java 8( 1.8.0_121)和Mockito 1.10.19。

I used Java 8 (1.8.0_121) together with Mockito 1.10.19.

这篇关于如何测试JavaFX(MVC)控制器逻辑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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