我们如何正确地为 JavaFX 控制器逻辑编写单元/集成测试?假设我正在测试的 Controller 类名为LoadController
,它的单元测试类是LoadControllerTest
,我的困惑源于:
如果
LoadControllerTest
该类LoadController
通过 I 实例化一个新对象,LoadController loadController = new LoadController();
则可以通过(许多)setter 将值注入控制器。这似乎是使用反射(遗留代码)的唯一方法。如果我不将值注入 FXML 控件,那么这些控件显然还没有初始化,返回 null。如果我改为使用
FXMLLoader
'loader.getController()
方法来检索loadController
它,它将正确初始化 FXML 控件,但控制器initialize()
会因此被调用,这会导致运行速度非常慢,并且由于无法注入模拟的依赖项,因此它更像是一个编写不佳的集成测试.
我现在正在使用前一种方法,但是有更好的方法吗?
测试外汇
这里的答案涉及@Tests
基于主应用程序start
方法而不是Controller 类的 TestFX。它显示了一种测试控制器的方法
verifyThat("#email", hasText("test@gmail.com"));
但这个答案涉及DataFX - 而我只是询问 JavaFX 的 MVC 模式。大多数 TestFX 讨论都集中在它的 GUI 功能上,所以我很好奇它是否也适合控制器。
下面的示例显示了我如何向控制器注入 aVBox
以使其在测试期间不为空。有没有更好的办法?请具体
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 ...
}
更新
这是一个基于 hotzst 建议的完整演示,但它返回此错误:
org.mockito.exceptions.base.MockitoException:无法实例化名为 'loadController' 类型的 'class com.mypackage.LoadController' 的 @InjectMocks 字段。您没有在字段声明时提供实例,所以我尝试构建实例。但是构造函数或初始化块抛出异常: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!
}
}
}