如何测试JavaFX(MVC)控制器逻辑?
我们如何正确地为JavaFX控制器逻辑编写单元/集成测试? 假设我正在测试的控制器类名为如何测试JavaFX(MVC)控制器逻辑?,java,unit-testing,javafx,controller,testfx,Java,Unit Testing,Javafx,Controller,Testfx,我们如何正确地为JavaFX控制器逻辑编写单元/集成测试? 假设我正在测试的控制器类名为LoadController,它的单元测试类名为LoadControllerTest,我的困惑源于: 如果LoadControllerTest类通过 LoadController LoadController=新的LoadController()我可以 然后通过(多个)设置器将值注入控制器。这似乎是使用反射(遗留代码)的唯一方法。如果我没有将这些值注入FXML控件,那么这些控件显然还没有初始化,返回null
LoadController
,它的单元测试类名为LoadControllerTest
,我的困惑源于:
- 如果
类通过LoadControllerTest
LoadController LoadController=新的LoadController()代码>我可以 然后通过(多个)设置器将值注入控制器。这似乎是使用反射(遗留代码)的唯一方法。如果我没有将这些值注入FXML控件,那么这些控件显然还没有初始化,返回null
- 如果我改为使用
的fxmloader
方法检索loader.getController()
,它将正确初始化FXML控件,但 因此会调用控制器的loadController
,这会导致运行非常缓慢,而且由于无法注入模拟依赖项,这更像是一个编写糟糕的集成测试initialize()
启动
方法而不是控制器类进行@Tests
。文中给出了一种测试控制器的方法
verifyThat("#email", hasText("test@gmail.com"));
但是这个答案涉及到DataFX,而我只是问一下JavaFX的MVC模式。大多数关于TestFX的讨论都集中在它的GUI功能上,所以我很好奇它是否也是控制器的理想选择
下面的示例显示了如何向控制器注入VBox
,以便在测试过程中不为null。有更好的办法吗?请具体说明
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));
}
}
更新 下面是一个基于hotzst建议的完整演示,但它返回以下错误: org.mockito.exceptions.base.MockitoException:无法实例化 @InjectMocks字段名为“loadController”,类型为“class com.mypackage.loadController”。你 没有在字段声明中提供实例,所以我尝试 构造实例。但是,构造函数或初始化 块引发异常:null
您可以使用类似的测试框架将依赖项注入控制器中。因此,您可能可以放弃大多数设置器,至少是那些仅用于方便测试的设置器 按照您提供的示例代码,我调整了测试中的类(为
FileSorter
定义一个内部类):
@FXML
注释在这里没有任何意义,因为没有附加任何FXML文件,但它似乎对代码或测试没有任何影响
然后,您的测试类可以如下所示:
@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();
}
}
此测试通过以下输出成功运行:
非空VBOX
可以使用@Rule
JavaFXThreadingRule
,因为在这样的测试中,您没有运行应在JavaFX线程中执行的任何代码部分
@Mock
注释与MockitoJUnitRunner
一起创建一个模拟实例,然后将其注入到用@InjectMocks
注释的实例中
可以找到一个优秀的教程。在测试中也有其他的模拟框架,比如和,但是Mockito是我最熟悉的
我将Java8(1.8.0121)与Mockito 1.10.19一起使用。对不起,我不知道这是如何回答我的问题的。您只演示了如何实例化模拟的
mainVBox
,但没有演示如何将其注入loadController对象,这正是我所困惑的。您说Mockito可以做到这一点,但您的示例没有显示如何将模拟的VBox
注入LoadController
类(SUT)。另外,我假设我继续执行loadController.initialize(url,rb)代码>在设置中
方法?你的答案还没有教给我任何东西,所以我无法对它进行升级,请澄清,如果(mainVBox!=null){…
,我的SUT在这一行上仍然会返回null,因为它的值没有设置或模拟。@Mathomatic,你是对的,代码遗漏了那一位(@InjectMocks
注释),我现在添加了一些解释。谢谢,但是模拟的FileSorter
似乎没有被注入,因为被测试的类仍然试图实例化它!我已经更新了我的问题,以包括我收到的错误以及完整的“工作”演示显示了错误发生的位置。我做错了什么?我无法重现您提到的错误,但是我必须稍微调整一下您的示例:FileSorter
未知,所以我使用了一个空的静态内部类,在测试中可以省略JavaFXThreadingRule
。@hotzst您是说您得到了我的cod吗要正确工作,请打印“非空VBOX”
?我知道你没有FileSorter
类,但它只是代表我可能拥有的任何私人安装的类。我的控制器碰巧有10个类似的实例化,但@Mock
注释似乎对它们无效。为什么FileSorter
会创建一个真正的对象这里没有使用测试的模拟?太奇怪了!@hotzst如果我简单地注释掉private FileSorter FileSorter=new FileSorter();
,那么代码就会工作,返回“非空VBOX”
。为什么@Mock private FileSorter FileSorter;
代码没有生效?是的,当运行测试时,它成功运行,并且我在控制台上打印的输出确实是on-NULL VBOX
。FileSorter
不应该引起问题,即使在用new实例化变量时,这应该是be由主运行中心覆盖
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!
}
}
}
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 {}
}
@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();
}
}