【发布时间】:2017-06-03 14:42:23
【问题描述】:
我们如何正确地为 JavaFX 控制器逻辑编写单元/集成测试?
假设我正在测试的Controller类名为LoadController,它的单元测试类是LoadControllerTest,我的困惑源于:
如果
LoadControllerTest类通过以下方式实例化一个新的LoadController对象LoadController loadController = new LoadController();我可以 然后通过(许多)设置器将值注入控制器。这似乎是使用反射(遗留代码)的唯一方法。如果我不将值注入 FXML 控件,那么这些控件显然还没有初始化,返回 null。如果我改为使用
FXMLLoader的loader.getController()方法来检索loadController,它将正确初始化FXML 控件,但 因此调用了控制器的initialize(),这导致运行非常缓慢,并且由于无法注入模拟的依赖项,因此它更像是一个编写不佳的集成测试。
我现在正在使用前一种方法,但是有更好的方法吗?
TestFX
答案here 涉及TestFX,它具有基于主应用程序的start 方法不是 Controller 类的@Tests。它显示了一种使用
verifyThat("#email", hasText("test@gmail.com"));
但这个答案涉及 DataFX - 而我只是询问 JavaFX 的 MVC 模式。大多数 TestFX 讨论都集中在它的 GUI 功能上,所以我很好奇它是否也适合控制器。
以下示例显示了我如何使用VBox 注入控制器,以便在测试期间它不为空。有没有更好的办法?请具体
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:无法实例化 @InjectMocks 字段名为'loadController',类型为'class com.mypackage.LoadController'。你 没有在字段声明中提供实例,所以我试图 构造实例。但是构造函数或初始化 块抛出异常: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!
}
}
}
【问题讨论】:
-
我无法重现您提到的错误,但是我不得不稍微调整您的示例:
FileSorter未知,所以我使用了一个空的静态内部类,在测试中JavaFXThreadingRule可以省略。 -
@hotzst 你是说我的代码可以正常工作,打印
"NON-NULL VBOX"?我知道您没有FileSorter类,但这只是为了代表我可能拥有的任何私人实例。我的控制器恰好有大约 10 个类似的实例化,但@Mock注释似乎没有对它们生效。为什么FileSorter会在这里创建一个真实的对象而不使用测试的模拟?好奇怪! -
@hotzst 如果我只是注释掉
private FileSorter fileSorter = new FileSorter();,那么代码就可以工作,返回"NON-NULL VBOX"。为什么@Mock private FileSorter fileSorter;代码不生效? -
是的,运行测试时它成功运行,我在控制台上打印的输出确实是
ON-NULL VBOX。fileSorter不应该造成问题,即使在使用 new 实例化变量时,它应该被测试中的模拟覆盖。您可以尝试将初始化移动到构造函数中。 -
@hotzst 你已经证明了你的观点,应该得到 +1。虽然,我觉得我最初的一些困惑仍然存在,例如......这是最合适的方法吗?二传手很笨重,但也许他们更好地保持封装。
@InjectMocks似乎是类似于通过WhiteBox注入的反射黑客。想法?
标签: java unit-testing javafx controller testfx