/ JavaFX / The View Objects Pattern & automated tests with TestFX

The View Objects Pattern & automated tests with TestFX

Hendrik on 2014/10/01 - 21:05 in JavaFX

When developing an application you should add automated tests. Oh, sorry – I mean you MUST add automated test. So when developing a JavaFX application there will come the moment when you ask yourself “How should I test this UI? A normal JUnit tests won’t work here…”

TestFX basics

That’s right but the JavaFX community is prepared for this problem by offering TestFX. With TestFX you can create unit tests for JavaFX applications. Let’s imagine you have an application that only contains a login dialog:
login
You can automatically test this dialog by using the TestFX API. I coded test might look like this:


As you can see you can control and fake all user events by using TestFX. At github you can find a general documentation of the API.

Dialog Objects Pattern

Mostly your application will contain more than a simple login dialog and in that case a test could become confusing:


Web developers already know this problem and introduced a pattern to avoid it: PageObject
Since we don’t have pages in JavaFX applications I would call it “View Objects Pattern” instead. By using this pattern you will define a class / object for each view in your application. Let’s image we have a music application with the following workflow:
workflow
The applications contains 4 different views. To write tests for the application we should create a view object for each view. Here is an pseudo code example for the album overview:


You can see some important facts in the code:

  • Each user interaction is defined as a method
  • The class provides methods to check important states
  • Each method returns the view object for the page that is visible after the method has been executed
  • If the view won’t change by calling a method the method will return “this

By doing so it is very easy to write understandable tests. Because all the methods will return a view object you can use it as a fluent API:

13 POST COMMENT

Send Us A Message Here

Your email address will not be published. Required fields are marked *

13 Comments
  • 2015/01/16

    Hi, Can you please share the full working source code for the above example?
    Thanks

    TestFx
    Reply
    • 2015/01/19

      Hi,
      to get this working I needed to create my own TestFX artifacts because of a bug in TestFX. I will try if this is working now based on the last TestFX release. In this case I can publish the code.

      Reply
      • 2015/08/12

        Can I ask also for an full working example?

        Tobias Hochgürtel
        Reply
        • 2015/08/17

          I will try to find some time to post a complete example

          Reply
  • 2015/01/30

    Hello,

    I am trying to use this TestFX framework and have run into a snag which I am not sure how to fix.

    In the getRootNode I load my FXML view.

    Then I have two tests:

    @Test
    public void searchByPolicyName() throws Exception{

    boolean success = HTTPHandler.getInstance().login(“user”, “password”);
    assert(success);

    click(“#policyNameTextField”).type(“Some Name”);
    click(“#searchButton”);

    TableView tv = (TableView)find(“#resultsTable”);
    waitUntil(tv, TableViews.containsCell(“Some Name”));
    Assertions.verifyThat(“#resultsTable”, TableViews.containsCell(“Some Name”));
    }

    @Test
    public void searchByPolicyNumber() throws Exception{

    boolean success = HTTPHandler.getInstance().login(“user”, “password”);
    assert(success);

    click(“#policyNumberTextField”).type(“1234567”);
    click(“#searchButton”);
    TableView tv = (TableView)find(“#resultsTable”);
    waitUntil(tv, TableViews.containsCell(“1234567”));
    Assertions.verifyThat(“#resultsTable”, TableViews.containsCell(“1234567”));
    }

    If only one runs it will be successful, if I run both, then the first to run will succeed. The second test opens a second stage, and the first stage doesn’t close, so the waitUntil times out.

    I have tried adding a closeCurrentWindow() after the Assertions.verifyThat(“#resultsTable”, TableViews.containsCell(“1234567”));

    However the close fires before the assertion and then that test will timeout.

    Can you show how to test multiple tests on the same FXML view?

    Thanks!

    Derek
    Reply
  • 2015/01/30

    I have even tried breaking up the test and running as a Suite and get the same results.

    Derek
    Reply
    • 2015/01/31

      Can you post a small complete example?

      Reply
      • 2015/02/02

        public class SampleWorkspaceTest extends GuiTest{

        public Parent getRootNode() {

        FXMLLoader loader = new FXMLLoader();
        Parent node = null;
        try {
        node = loader.load(
        this.getClass().getResource(“/org/sample/workspace/SampleWorkSpace.fxml”).openStream());
        ControllerBase controller = (ControllerBase)loader.getController();
        controller.setPrimaryStage(stage);
        controller.initWorkspace();
        } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }

        return node;
        }

        @Before
        private void login() {
        ///log in to server
        }
        @Test
        public void searchByPolicyNumber() throws Exception{
        TableView tv = (TableView)find(“#resultsTable”);

        click(“#policyNumberTextField”).type(“123456”);
        click(“#searchButton”);
        waitUntil(tv, TableViews.containsCell(“123456”));
        Assertions.verifyThat(tv, TableViews.containsCell(“123456”));

        }
        @Test
        public void searchByPolicyName() throws Exception{
        TableView tv = (TableView)find(“#resultsTable”);

        click(“#policyNameTextField”).type(“Policy Name”);
        click(“#searchButton”);
        waitUntil(tv, TableViews.containsCell(“Policy Name”), 30);
        Assertions.verifyThat(tv, TableViews.containsCell(“Policy Name”));

        }
        }

        The system logs in successfully, and runs the test without issue. However the second test (in this case searchByPolicyName) – times out on the waitUntil()… All UI interaction is with the correct view/stage, but the wait times out with the following error:

        java.lang.RuntimeException: java.util.concurrent.TimeoutException
        at org.loadui.testfx.utils.TestUtils.awaitCondition(TestUtils.java:49)
        at org.loadui.testfx.GuiTest.waitUntil(GuiTest.java:364)
        at org.idahosif.test.SampleWorkspaceTest.searchByPolicyName(SampleWorkspaceTest.java:63)
        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:497)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
        Caused by: java.util.concurrent.TimeoutException
        at org.loadui.testfx.utils.TestUtils.awaitCondition(TestUtils.java:43)
        … 26 more

        When I perform a findAll(“#resultsTable”) – I get one on the first test, and two on the second test, which I assume is the reason for the timeout – it’s looking at the first table in the hierarchy, rather than the later.

        Additional error that I get – which I can’t figure out is after it loads the FXML and before any tests are executed, the following error is logged to the console, but it doesn’t appear to cause any issues…

        java.util.concurrent.TimeoutException: Timeout waiting for task.
        at com.google.common.util.concurrent.AbstractFuture$Sync.get(AbstractFuture.java:276)
        at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:96)
        at org.loadui.testfx.utils.FXTestUtils.invokeAndWait(FXTestUtils.java:131)
        at org.loadui.testfx.utils.FXTestUtils.invokeAndWait(FXTestUtils.java:156)
        at org.loadui.testfx.GuiTest.showNodeInStage(GuiTest.java:123)
        at org.loadui.testfx.GuiTest.showNodeInStage(GuiTest.java:101)
        at org.loadui.testfx.GuiTest.setupStage(GuiTest.java:91)
        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:497)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

        I am not sure if this gives you the data you need – however I am at a loss on why the TestFX framework isn’t tearing down correctly – and how I can manage it.

        If I attempt to add closeCurrentWindow() in the searchByPolicyNumber() method, at the end, it fires before the verifyThat executes, which I am guessing is threading related and the window closes, the second test doesn’t execute and the first hangs.

        Derek
        Reply
        • 2015/02/03

          You should open an issue for TestFX. To do my stuff I created my own version of TestFX by adding some bugfixes for open bugs. But that was more than half a year ago. So I don’t know the current state of TestFX. (since JavaOne I pan to have a deeper look into the library but didn’t find time until now…)

          Reply
          • 2015/02/03

            I will do that thanks – appreciate you reviewing to see if I was doing something incorrect.

            Derek
          • 2015/02/03

            I think the code looks correct. By I don’t try it. I just looked at it. It’s the way how I would handle this 😉

  • 2015/03/18

    Hi, I’m trying implement a litter test for the code from your first tutorial about DataFX but How I should initialize the test so I can use clickOn ?

    Tulio
    Reply
    • 2015/03/20

      You need to extend the GuiTest class of TestFX and start the flow in the getRootNode() method. Then you can return the flow view.

      Reply